Ruby передается по ссылке или по значению?

210
@user.update_languages(params[:language][:language1], 
                       params[:language][:language2], 
                       params[:language][:language3])
lang_errors = @user.errors
logger.debug "--------------------LANG_ERRORS----------101-------------" 
                + lang_errors.full_messages.inspect

if params[:user]
  @user.state = params[:user][:state]
  success = success & @user.save
end
logger.debug "--------------------LANG_ERRORS-------------102----------" 
                + lang_errors.full_messages.inspect

if lang_errors.full_messages.empty?

@user объект добавляет ошибки в переменную lang_errors в методе update_lanugages. когда я выполняю сохранение объекта @user, я теряю ошибки, которые были первоначально сохранены в переменной lang_errors.

Хотя то, что я пытаюсь сделать, было бы скорее взломом (который, похоже, не работает). Я хотел бы понять, почему переменные значения вымываются. Я понимаю, что передаю по ссылке, поэтому я хотел бы знать, как значение может храниться в этой переменной без размывания.

  • 0
    Я также заметил, что я могу сохранить это значение в клонированном объекте
  • 1
    Вы должны посмотреть на ответ Эйба Фолькера. Но после бега вокруг блока вот как я бы сказал. когда вы передаете объект Foo в процедуру, копия ссылки на объект передается, bar, Pass by value. Вы не можете изменить объект, на который указывает Foo, но вы можете изменить содержимое объекта, на который он указывает. Таким образом, если вы передаете массив, содержимое массива можно изменить, но вы не можете изменить, на какой массив ссылаются. Приятно иметь возможность использовать методы Foo, не беспокоясь о том, чтобы испортить другие зависимости от Foo.
Теги:
pass-by-reference

12 ответов

213
Лучший ответ

В традиционной терминологии Ruby строго передается по значению. Но это не то, о чем вы здесь спрашиваете.

Ruby не имеет понятия чистого, не ссылочного значения, поэтому вы, конечно же, не можете передать его методу. Переменные всегда ссылаются на объекты. Чтобы получить объект, который не будет меняться из-под вас, вам необходимо выполнить дуплекс или клонировать переданный вами объект, тем самым предоставив объект, к которому никто не имеет ссылки. (Даже это не является пуленепробиваемым, однако - оба стандартных метода клонирования выполняют мелкую копию, поэтому переменные экземпляра клона все еще указывают на те же объекты, что и оригиналы. Если объекты, на которые ссылаются ivars, мутируют, это будет все еще отображаются в копии, поскольку она ссылается на одни и те же объекты.)

  • 0
    Спасибо. Я до сих пор не уверен, что это полностью полезная функция. Не будет ли полезен явный метод передачи по значению, хотя все является объектом. Другое дело, что я обнаружил, что клонированная копия имеет другой object_id, поэтому разве это не делает его совершенно равнодушным к исходному объекту, хотя оно может содержать значения исходного объекта или мои принципы полностью ошибочны здесь?
  • 3
    Да, клон является полностью независимым объектом. Что касается передачи по значению, то она не совсем совместима с чистым ОО, где «значения» не существуют, кроме как в виде состояния объекта. Самое близкое, что вы можете получить, это что-то вроде модификатора типа bycopy Objective-C, который сообщает среде выполнения сделать закулисную копию. Это звучит полезно.
Показать ещё 17 комментариев
363

Другие ответчики все правильны, но друг попросил меня объяснить это ему, и то, что это действительно сводится к тому, как Ruby обрабатывает переменные, поэтому я решил поделиться некоторыми простыми картинками/объяснениями, которые я написал для него (извинения для длины и, вероятно, некоторого упрощения):


Q1: Что происходит, когда вы назначаете новую переменную str значению 'foo'?

str = 'foo'
str.object_id # => 2000

Изображение 4161

A: Создается метка с именем str, которая указывает на объект 'foo', который для состояния этого интерпретатора Ruby находится в ячейке памяти 2000.


Q2: Что произойдет, если назначить существующую переменную str новому объекту с помощью =?

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002

Изображение 4162

A: метка str теперь указывает на другой объект.


Q3: Что происходит, когда вы назначаете новую переменную = на str?

str2 = str
str2.object_id # => 2002

Изображение 4163

A: Создается новый ярлык с именем str2, который указывает на тот же объект, что и str.


Q4: Что произойдет, если объект, на который ссылаются str и str2, будет изменен?

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002

Изображение 4164

A: Обе метки по-прежнему указывают на один и тот же объект, но сам этот объект мутировал (его содержимое изменилось как нечто другое).


Как это относится к исходному вопросу?

Это в основном то же самое, что и в Q3/Q4; метод получает свою собственную частную копию переменной /label (str2), которая передается ей (str). Он не может изменить тот объект, на который указывает метка str, но может изменить содержимое объекта, для которого они ссылаются, содержать else:

str = 'foo'

def mutate(str2)
  puts "str2: #{str2.object_id}"
  str2.replace 'bar'
  str2 = 'baz'
  puts "str2: #{str2.object_id}"
end

str.object_id # => 2004
mutate(str) # str2: 2004, str2: 2006
str # => "bar"
str.object_id # => 2004
  • 1
    Роберт Хитон также недавно написал в блоге об этом: robertheaton.com/2014/07/22/…
45

Проходит ли Ruby по ссылке или по значению?

Ruby - это пропускная способность. Всегда. Без исключений. Нет, если. Нет, но.

Вот простая программа, которая демонстрирует этот факт:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
  • 14
    @DavidJ .: «Ошибка здесь в том, что локальный параметр переназначается (указывает на новое место в памяти)» - это не ошибка, это определение передачи по значению . Если бы Ruby передавался по ссылке, то переназначение привязки аргумента локального метода в вызываемом объекте также переназначило бы привязку локальной переменной в вызывающей стороне. Что это не так. Ergo, Ruby передается по значению. Тот факт, что если вы изменяете изменяемое значение, то это значение совершенно не имеет значения, именно так работает изменяемое состояние. Ruby не является чисто функциональным языком.
  • 5
    Спасибо Йоргу, чтобы защитить истинное определение «передача по стоимости». Ясно, что наш мозг тает, когда значение на самом деле является ссылкой, хотя рубин всегда проходит мимо значения.
Показать ещё 19 комментариев
35

Ruby использует "pass by object reference"

(с использованием терминов Python.)

Говорить, что Ruby использует "pass by value" или "pass by reference", на самом деле недостаточно описателен, чтобы быть полезным. Я думаю, что большинство людей знают это в наши дни, эта терминология ( "значение" и "ссылка" ) происходит от С++.

В С++ "передать по значению" означает, что функция получает копию переменной, а любые изменения в копии не меняют оригинал. Это верно и для объектов. Если вы передаете объектную переменную по значению, то весь объект (включая все его члены) будет скопирован, и любые изменения членов не изменят эти элементы на исходном объекте. (Это отличается, если вы передаете указатель по значению, но Ruby не имеет указателей в любом случае AFAIK.)

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

Вывод:

in inc: 6
in main: 5
in inc: 1
in main: 1

В С++ "передать по ссылке" означает, что функция получает доступ к исходной переменной. Он может назначить целое новое целое число буквально, и исходная переменная также будет иметь это значение.

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

Вывод:

in replace: 10
in main: 10

Ruby использует pass by value (в смысле С++), если аргумент не является объектом. Но в Ruby все является объектом, поэтому в языке С++ в Ruby действительно нет пропуска по значению.

В Ruby используется "pass by object reference" (для использования терминологии Python):

  • Внутри функции любой член объекта может иметь новые значения, назначенные им, и эти изменения будут сохраняться после возвращения функции. *
  • Внутри функции назначение целого нового объекта переменной приводит к тому, что переменная перестает ссылаться на старый объект. Но после возвращения функции исходная переменная все равно будет ссылаться на старый объект.

Поэтому Ruby не использует "pass by reference" в смысле С++. Если это так, то присвоение нового объекта переменной внутри функции приведет к тому, что старый объект будет забыт после возвращения функции.

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

Вывод:

1
2
2
3
2

1
2
1

* Вот почему в Ruby, если вы хотите изменить объект внутри функции, но забудьте о тех изменениях, когда функция вернется, тогда вы должны явно сделать копию объекта перед внесением временных изменений в копию.

  • 0
    Твой ответ самый лучший. Я также хочу опубликовать простой пример def ch(str) str.reverse! end; str="abc"; ch(str); puts str #=> "cba"
  • 0
    Это правильный ответ! Это также очень хорошо объяснено здесь: robertheaton.com/2014/07/22/… . Но что я до сих пор не понимаю, так это: def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}" . Это печатает "Рубин передается по значению". Но переменная внутри foo переназначается. Если bar будет массивом, переназначение не повлияет на baz . Зачем?
Показать ещё 1 комментарий
16

Есть уже некоторые отличные ответы, но я хочу опубликовать определение пары властей по этому вопросу, но также надеясь, что кто-то может объяснить, что сказали властям Мац (создатель Ruby) и Дэвид Фланаган в их отличном O ' Reilly book, Язык программирования Ruby.

[из 3.8.1: Ссылки на объекты]

Когда вы передаете объект методу в Ruby, это ссылка на объект, передаваемая методу. Это не сам объект, и это не ссылка на ссылку на объект. Другой способ сказать, что аргументы метода передаются по значению, а не по ссылке, но что переданные значения являются объектными ссылками.

Поскольку ссылки на объекты передаются методам, методы могут использовать эти ссылки для изменения базового объекта. Эти изменения затем видны, когда метод возвращается.

Это все имеет смысл для меня до последнего абзаца, и особенно это последнее предложение. Это в лучшем случае вводит в заблуждение и в худшем случае путается. Как каким-либо образом могут быть внесены изменения в эту ссылку на значение по умолчанию?

  • 1
    Потому что ссылка не изменяется; базовый объект
  • 1
    Потому что объект изменчив. Руби не является чисто функциональным языком. Это полностью ортогонально передаче по ссылке или передаче по значению (за исключением того факта, что в чисто функциональном языке передача по значению и передача по ссылке всегда дают одинаковые результаты, поэтому язык может использовать один или оба без вашего ведома).
Показать ещё 1 комментарий
14

Ruby - это пропускная способность в строгом смысле слова, НО значения являются ссылками.

Это можно назвать " pass-reference-by-value". В этой статье есть лучшее объяснение, которое я прочитал: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

Ссылка на ссылку по ссылке может быть кратко объяснена следующим образом:

Функция получает ссылку (и будет получать доступ) к тому же объекту в памяти, который используется вызывающим. Однако он не получает поле, в котором вызывающий объект хранит этот объект; как и в переменной pass-value-by-value, функция предоставляет свой собственный поле и создает для себя новую переменную.

Полученное поведение на самом деле представляет собой комбинацию классических определений pass-by-reference и pass-by-value.

  • 0
    «передать ссылку по значению» - это та же фраза, которую я использую для описания передачи аргументов Руби. Я думаю, что это самая точная и лаконичная фраза.
14

Проходит ли Ruby по ссылке или по значению?

Ruby является передачей по ссылке. Всегда. Без исключений. Нет, если. Нет, но.

Вот простая программа, которая демонстрирует этот факт:

def foo(bar)
  bar.object_id
end

baz = 'value'

puts "#{baz.object_id} Ruby is pass-by-reference #{foo(baz)} because object_id (memory addresses) are always the same ;)"

= > 2279146940 Ruby является передачей по ссылке 2279146940, потому что object_id (адреса памяти) всегда одинаковы;)

def bar(babar)
  babar.replace("reference")
end

bar(baz)

puts "some people don't realize it reference because local assignment can take precedence, but it clearly pass-by-#{baz}"

= > некоторые люди не понимают, что это ссылка, потому что локальное назначение может иметь приоритет, но оно явно передается по ссылке

  • 0
    Это единственный правильный ответ, и он дает несколько приятных замечаний: Try a = 'foobar'; б = а; b [5] = 'z', a и b будут изменены.
  • 2
    @Martijn: ваш аргумент не совсем верен. Давайте пройдемся по вашему заявлению кода за заявлением. a = 'foobar' создает новую ссылку, указывающую на 'foobar'. b = a создает вторую ссылку на те же данные, что и a. b [5] = 'z' меняет шестой символ значения, на которое ссылается b, на 'z' (значение, на которое по совпадению также ссылается a, изменяется). Вот почему «оба модифицируются» в ваших терминах, или точнее, почему «значение, на которое ссылаются обе переменные, модифицируется».
Показать ещё 6 комментариев
8

Параметры - это копия исходной ссылки. Таким образом, вы можете изменять значения, но не можете изменить исходную ссылку.

1
Two references refer to same object as long as there is no reassignment. 

Любые обновления одного и того же объекта не будут ссылаться на новую память, поскольку она все еще находится в одной памяти.   Вот несколько примеров:

    a = "first string"
    b = a



    b.upcase! 
    => FIRST STRING
    a
    => FIRST STRING

    b = "second string"


a
    => FIRST STRING
    hash = {first_sub_hash: {first_key: "first_value"}}
first_sub_hash = hash[:first_sub_hash]
first_sub_hash[:second_key] = "second_value"

    hash
    => {first_sub_hash: {first_key: "first_value", second_key: "second_value"}}

    def change(first_sub_hash)
    first_sub_hash[:third_key] = "third_value"
    end

    change(first_sub_hash)

    hash
    =>  {first_sub_hash: {first_key: "first_value", second_key: "second_value", third_key: "third_value"}}
1

Следует отметить, что вам не нужно даже использовать метод "replace" для изменения значения исходного значения. Если вы присвоите одно из хэш-значений для хеша, вы меняете исходное значение.

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"
  • 0
    Еще одна вещь, которую я нашел. Если вы передаете числовой тип, все числовые типы являются неизменяемыми и, следовательно, передаются по значению. Функция замены, которая работала со строкой выше, НЕ работает ни для одного из числовых типов.
1

Попробуйте следующее: -

1.object_id
#=> 3

2.object_id
#=> 5

a = 1
#=> 1
a.object_id
#=> 3

b = 2
#=> 2
b.object_id
#=> 5

идентификатор a содержит object_id 3 для объекта значения 1 и идентификатор b содержит object_id 5 для объекта значения 2.

Теперь сделайте следующее: -

a.object_id = 5
#=> error

a = b
#value(object_id) at b copies itself as value(object_id) at a. value object 2 has object_id 5
#=> 2

a.object_id 
#=> 5

Теперь, a и b оба содержат одинаковый object_id 5, который ссылается на объект значения 2. Итак, переменная Ruby содержит object_ids для ссылки на объекты значений.

Выполнение следующего также дает ошибку: -

c
#=> error

но это не даст ошибки: -

5.object_id
#=> 11

c = 5
#=> value object 5 provides return type for variable c and saves 5.object_id i.e. 11 at c
#=> 5
c.object_id
#=> 11 

a = c.object_id
#=> object_id of c as a value object changes value at a
#=> 11
11.object_id
#=> 23
a.object_id == 11.object_id
#=> true

a
#=> Value at a
#=> 11

Здесь идентификатор возвращает объект 11 значений, идентификатор объекта которого равен 23, то есть object_id 23 находится в идентификаторе a. Теперь мы видим пример с использованием метода.

def foo(arg)
  p arg
  p arg.object_id
end
#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23

arg в foo присваивается возвращаемое значение x. Он ясно показывает, что аргумент передается значением 11, а значение 11 само является объектом, имеет уникальный идентификатор объекта 23.

Теперь см. также: -

def foo(arg)
  p arg
  p arg.object_id
  arg = 12
  p arg
  p arg.object_id
end

#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23
#=> 12
#=> 25
x
#=> 11
x.object_id
#=> 23

Здесь идентификатор arg сначала содержит object_id 23 для ссылки 11 и после внутреннего присвоения с объектом значения 12, он содержит object_id 25. Но он не изменяет значение, на которое ссылается идентификатор x, используемый в методе вызова.

Следовательно, Ruby передается по значению, а переменные Ruby не содержат значений, но содержат ссылку на объект значения.

1

Ruby интерпретируется. Переменные - это ссылки на данные, но не сами данные. Это облегчает использование одной и той же переменной для данных разных типов.

Назначение lhs = rhs затем копирует ссылку на rhs, а не данные. Это отличается на других языках, таких как C, где присваивание копирует данные в lhs из rhs.

Итак, для вызова функции переменная, переданная, скажем, x, действительно скопирована в локальную переменную в функции, но x является ссылкой. Затем будут две копии справки, причем оба они ссылаются на одни и те же данные. Один будет в вызывающем, один в функции.

Присвоение функции затем копирует новую ссылку на версию функции x. После этого версия хэндлера x остается неизменной. Это все еще ссылка на исходные данные.

Напротив, использование метода .replace в x приведет к тому, что ruby ​​сделает копию данных. Если замена используется перед любыми новыми назначениями, то в действительности вызывающий элемент также увидит изменение данных в своей версии.

Аналогично, если исходная ссылка находится в такте для переданной переменной, переменные экземпляра будут такими же, что и вызывающий. В рамках объекта переменные экземпляра всегда имеют самые последние опорные значения, независимо от того, предоставляются ли они вызывающим или заданы в функции, в которую был передан класс.

"Вызов по значению" или "вызов по ссылке" здесь путают из-за путаницы над '=' В скомпилированных языках '=' является копией данных. Здесь в этом интерпретируемом языке '=' является ссылочной копией. В примере у вас есть ссылка, переданная в сопровождении ссылочной копии, хотя "=", которая сгибает оригинал, переданный в ссылке, а затем люди, говорящие об этом, как будто "=" были копией данных.

Чтобы соответствовать определениям, мы должны придерживаться ".replace", поскольку это копия данных. С точки зрения ".replace" мы видим, что это действительно проходит по ссылке. Кроме того, если мы проходим через отладчик, мы видим, что ссылки передаются, поскольку переменные являются ссылками.

Однако, если мы должны сохранить '=' в качестве системы отсчета, то действительно мы видим, что данные передаются в данных вплоть до назначения, и после этого мы не можем видеть это после присвоения, в то время как данные вызывающего абонента остается неизменной. На уровне поведения это пропуск по значению, если мы не считаем переданный в значение составным, поскольку мы не сможем сохранить его часть при изменении другой части в одном задании (так как это присвоение изменяет ссылку, а оригинал выходит за рамки). Также будет бородавка, в этом случае переменными в объектах будут ссылки, как и все переменные. Следовательно, мы будем вынуждены говорить о передаче "ссылок по значению" и должны использовать связанные установки.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню