Ruby передается по ссылке или по значению?
@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
переменная.
Хотя то, что я пытаюсь сделать, было бы скорее хаком (который, похоже, не работает). Я хотел бы понять, почему значения переменных вымываются. Я понимаю, что передача по ссылке, поэтому я хотел бы знать, как значение может храниться в этой переменной, не стираясь.
14 ответов
В традиционной терминологии Ruby строго передается по значению. Но это не совсем то, что вы спрашиваете здесь.
Ruby не имеет понятия чистого, не ссылочного значения, поэтому вы определенно не можете передать его методу. Переменные всегда являются ссылками на объекты. Чтобы получить объект, который не изменится из-под вас, вам нужно дублировать или клонировать объект, который вы передали, таким образом давая объект, на который никто больше не ссылается. (Однако даже это не является пуленепробиваемым - оба стандартных метода клонирования делают поверхностное копирование, поэтому переменные экземпляра клона по-прежнему указывают на те же объекты, что и оригиналы. Если объекты, на которые ссылается ивар, видоизменяются, это будет по-прежнему отображаются в копии, поскольку ссылаются на те же объекты.)
Все остальные ответчики правы, но друг попросил меня объяснить ему это, и что на самом деле сводится к тому, как Ruby обрабатывает переменные, поэтому я решил поделиться некоторыми простыми картинками / объяснениями, которые я написал для него (извинения за длину и, вероятно, некоторое упрощение)
Q1: что происходит, когда вы назначаете новую переменную str
до значения 'foo'
?
str = 'foo'
str.object_id # => 2000
A: ярлык называется str
создан, что указывает на объект 'foo'
, который для состояния этого интерпретатора Ruby находится в ячейке памяти 2000
,
Q2: что происходит, когда вы назначаете существующую переменную str
на новый объект с помощью =
?
str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002
A: этикетка str
теперь указывает на другой объект.
Q3: что происходит, когда вы назначаете новую переменную =
в str
?
str2 = str
str2.object_id # => 2002
A: Новый лейбл под названием str2
создается, что указывает на тот же объект, что и str
,
Q4: что произойдет, если объект, на который ссылается str
а также str2
меняется?
str2.replace 'baz'
str2 # => 'baz'
str # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002
A: Обе метки по-прежнему указывают на один и тот же объект, но сам объект мутировал (его содержимое изменилось на что-то другое).
Как это связано с первоначальным вопросом?
В основном это то же самое, что и в Q3/Q4; метод получает свою собственную копию переменной / метки (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
Ruby использует "передачу по ссылке на объект"
(Используя терминологию Python.)
Сказать, что в Ruby используется "передача по значению" или "передача по ссылке", на самом деле недостаточно, чтобы быть полезным. Я думаю, что, как большинство людей знают это в наши дни, эта терминология ("значение" против "ссылка") происходит из C++.
В C++ "передача по значению" означает, что функция получает копию переменной, и любые изменения в копии не изменяют оригинал. Это верно и для объектов. Если вы передаете переменную объекта по значению, тогда весь объект (включая все его члены) копируется, и любые изменения в элементах не изменяют эти элементы в исходном объекте. (Другое дело, если вы передаете указатель по значению, но в 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
В C++ "передача по ссылке" означает, что функция получает доступ к исходной переменной. Он может назначить целое новое литеральное целое число, и исходная переменная также будет иметь это значение.
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 использует передачу по значению (в смысле C++), если аргумент не является объектом. Но в Ruby все является объектом, так что на самом деле в Ruby отсутствует передача по значению в смысле C++.
В Ruby используется "передача по ссылке на объект" (для использования терминологии Python):
- Внутри функции любому члену объекта могут быть назначены новые значения, и эти изменения сохранятся после возврата из функции.*
- Внутри функции назначение целого нового объекта переменной приводит к тому, что переменная перестает ссылаться на старый объект. Но после возврата из функции исходная переменная все равно будет ссылаться на старый объект.
Поэтому в Ruby не используется "передача по ссылке" в смысле C++. Если это так, то назначение нового объекта переменной внутри функции приведет к тому, что старый объект будет забыт после возврата функции.
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, если вы хотите изменить объект внутри функции, но забыть об этих изменениях при возврате функции, вы должны явно сделать копию объекта, прежде чем вносить временные изменения в копию.
Ruby передается по ссылке или по значению?
Ruby передается по значению. Всегда. Без исключений. Нет, если Никаких "но.
Вот простая программа, которая демонстрирует этот факт:
def foo(bar)
bar = 'reference'
end
baz = 'value'
foo(baz)
puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
Ruby в строгом смысле передается по значению, НО значения являются ссылками.
Это можно назвать "передачей по значению". Эта статья имеет лучшее объяснение, которое я читал: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/
Передача-ссылка-значение может быть кратко объяснена следующим образом:
Функция получает ссылку (и получит доступ) к тому же объекту в памяти, который используется вызывающей стороной. Однако он не получает ящик, в котором вызывающая сторона хранит этот объект; как и в случае передачи значения по значению, функция предоставляет свой собственный блок и создает новую переменную для себя.
Получающееся поведение фактически является комбинацией классических определений передачи по ссылке и передачи по значению.
Уже есть несколько хороших ответов, но я хочу опубликовать определение пары авторитетов по этому вопросу, но также надеюсь, что кто-то может объяснить, что сказали авторитеты Матц (создатель Ruby) и Дэвид Фланаган в своей превосходной книге О'Рейли, Язык программирования Ruby.
[из 3.8.1: Ссылки на объекты]
Когда вы передаете объект методу в 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's (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's reference because local assignment can take precedence, but it's clearly pass-by-#{baz}"
=> некоторые люди не понимают, что это ссылка, потому что локальное назначение может иметь приоритет, но оно явно передается по ссылке
Параметры являются копией оригинальной ссылки. Таким образом, вы можете изменить значения, но не можете изменить исходную ссылку.
Следует отметить, что вам даже не нужно использовать метод "замена", чтобы изменить исходное значение. Если вы назначите одно из значений хеш-функции для хэша, вы измените исходное значение.
def my_foo(a_hash)
a_hash["test"]="reference"
end;
hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"
Попробуй это:--
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
Аргумент в foo присваивается с возвращаемым значением х. Это ясно показывает, что аргумент передается значением 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 не содержат значений, но содержат ссылку на объект-значение.
Множество отличных ответов, посвященных теории о том, как работает «передача по значению» в Ruby . Но я узнаю и понимаю все намного лучше на примере. Надеюсь, это будет полезно.
def foo(bar)
puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
bar = "reference"
puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end
bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"
# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 80 # <-----
bar (value) after foo with object_id 60 # <-----
Как видите, когда мы ввели метод, наша полоса по-прежнему указывала на строку «значение». Но затем мы присвоили строковому объекту «ссылку» на bar , который имеет новый object_id. В этом случае bar внутри foo имеет другую область видимости , и все, что мы передали внутри метода, больше не доступно для bar , поскольку мы переназначили его и указали на новое место в памяти, которое содержит String «ссылку».
Теперь рассмотрим этот же метод. Единственная разница в том, что делать внутри метода
def foo(bar)
puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
bar.replace "reference"
puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end
bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"
# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 60 # <-----
bar (reference) after foo with object_id 60 # <-----
Заметили разницу? Здесь мы сделали следующее: мы изменили содержимое объекта String, на который указывала эта переменная. Область действия bar по-прежнему отличается внутри метода.
Поэтому будьте осторожны с тем, как вы обрабатываете переменную, переданную в методы. А если вы модифицируете передаваемые переменные на месте (gsub!, replace и т.д.), то указывайте это в названии метода на ура ! , вроде так "def foo!"
PS:
Важно помнить, что «bar» внутри и снаружи foo — это «разные» «bar». Объем у них разный. Внутри метода вы можете переименовать «бар» в «клуб», и результат будет таким же.
Я часто вижу, как переменные повторно используются внутри и снаружи методов, и, хотя это нормально, это ухудшает читабельность кода и ИМХО является запахом кода. Я настоятельно рекомендую не делать то, что я сделал в моем примере выше :), а лучше сделать это
def foo(fiz)
puts "fiz (#{fiz}) entering foo with object_id #{fiz.object_id}"
fiz = "reference"
puts "fiz (#{fiz}) leaving foo with object_id #{fiz.object_id}"
end
bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"
# Output
bar (value) before foo with object_id 60
fiz (value) entering foo with object_id 60
fiz (reference) leaving foo with object_id 80
bar (value) after foo with object_id 60
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"}}
Руби интерпретируется. Переменные - это ссылки на данные, но не сами данные. Это облегчает использование одной и той же переменной для данных разных типов.
Присвоение lhs = rhs затем копирует ссылку на rhs, а не данные. Это отличается в других языках, таких как C, где присваивание копирует данные в lhs из rhs.
Таким образом, для вызова функции переданная переменная, скажем, x, действительно копируется в локальную переменную в функции, но x является ссылкой. Затем будет две копии ссылки, обе ссылаются на одни и те же данные. Один будет в звонящем, другой в функции.
Назначение в функции затем скопирует новую ссылку на версию функции x. После этого версия вызывающего абонента x остается неизменной. Это все еще ссылка на исходные данные.
Напротив, использование метода.replace для x приведет к тому, что ruby сделает копию данных. Если замена используется перед любыми новыми назначениями, то вызывающая сторона также увидит изменение данных в своей версии.
Точно так же, пока исходная ссылка находится в такте для переданной переменной in, переменные экземпляра будут такими же, как видит вызывающая сторона. В рамках объекта переменные экземпляра всегда имеют самые современные ссылочные значения, независимо от того, предоставляются ли они вызывающей стороной или задаются в функции, которой был передан класс.
'Вызов по значению' или 'Вызов по ссылке' здесь запутан из-за путаницы над '=' В скомпилированных языках '=' является копией данных. Здесь на этом интерпретируемом языке '=' является справочной копией. В этом примере передается ссылка, за которой следует ссылочная копия, хотя '=', которая перекрывает оригинал, переданный в ссылке, а затем люди говорят об этом, как будто '=' была копией данных.
Чтобы соответствовать определениям, мы должны придерживаться ".replace", так как это копия данных. С точки зрения ".replace" мы видим, что это действительно передача по ссылке. Кроме того, если мы пройдем через отладчик, мы увидим, что ссылки передаются, поскольку переменные являются ссылками.
Однако если мы должны сохранить '=' в качестве системы отсчета, тогда мы действительно увидим переданные данные вплоть до назначения, а затем мы больше не увидим их после назначения, в то время как данные вызывающей стороны остаются неизменными. На поведенческом уровне это передается по значению, пока мы не считаем переданное значение составным - поскольку мы не сможем сохранить его часть при изменении другой части в одном назначении (как это назначение изменяет ссылку и оригинал выходит за рамки видимости). Также будет бородавка, в этом случае переменные в объектах будут ссылками, как и все переменные. Следовательно, мы будем вынуждены говорить о передаче "ссылки по значению" и должны использовать связанные выражения.
Да, но....
Ruby передает ссылку на объект, и, поскольку все в Ruby является объектом, можно сказать, что оно передается по ссылке.
Я не согласен с публикациями здесь, утверждающими, что это передача по значению, что мне кажется педантичной симантической игрой.
Однако на самом деле он "скрывает" поведение, потому что большинство операций, которые Ruby предоставляет "из коробки" - например, строковые операции создают копию объекта:
> astringobject = "lowercase"
> bstringobject = astringobject.upcase
> # bstringobject is a new object created by String.upcase
> puts astringobject
lowercase
> puts bstringobject
LOWERCASE
Это означает, что большую часть времени исходный объект остается неизменным, создавая впечатление, что рубин "проходит по значению".
Конечно, при разработке собственных классов понимание деталей этого поведения важно как для функционального поведения, так и для эффективности памяти и производительности.