¿Pasa Ruby por referencia o por valor?

@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 objeto @user agrega errores a la variable lang_errors en el método update_lanugages . cuando realizo un save en el objeto @user , pierdo los errores que inicialmente estaban almacenados en la variable lang_errors .

Aunque lo que estoy intentando hacer sería más un hack (que no parece estar funcionando). Me gustaría entender por qué los valores variables se eliminan. Entiendo pasar por referencia, así que me gustaría saber cómo se puede mantener el valor en esa variable sin que se desvanezcan.

En la terminología tradicional, Ruby es estrictamente de paso por valor . Pero eso no es realmente lo que estás preguntando aquí.

Ruby no tiene ningún concepto de valor puro, sin referencia, por lo que ciertamente no se puede pasar a un método. Las variables son siempre referencias a objetos. Para obtener un objeto que no cambiará debajo de usted, necesita duplicar o clonar el objeto que le pasa, dando así un objeto al que nadie más tiene referencia. (Aunque esto no es a prueba de balas, ambos métodos de clonación estándar hacen una copia superficial, por lo que las variables de instancia del clon aún apuntan a los mismos objetos que los originales. Si los objetos a los que hacen referencia los ivars cambian, eso todavía aparece en la copia, ya que hace referencia a los mismos objetos).

Los otros respondedores son todos correctos, pero un amigo me pidió que le explicara esto y lo que realmente se reduce a cómo Ruby maneja las variables, así que pensé en compartir algunas imágenes simples / explicaciones que escribí para él (disculpas por la longitud) y probablemente alguna simplificación excesiva):


P1: ¿Qué sucede cuando asigna una nueva variable str a un valor de 'foo' ?

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

enter image description here

A: Se crea una etiqueta llamada str que apunta al objeto 'foo' , que para el estado de este intérprete de Ruby se encuentra en la ubicación de la memoria 2000 .


P2: ¿Qué sucede cuando asigna la variable existente str a un nuevo objeto usando = ?

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

enter image description here

A: La etiqueta str ahora apunta a un objeto diferente.


P3: ¿Qué sucede cuando asigna una nueva variable = a str ?

 str2 = str str2.object_id # => 2002 

enter image description here

A: Se crea una nueva etiqueta llamada str2 que apunta al mismo objeto que str .


P4: ¿Qué sucede si el objeto al que hacen referencia str y str2 se modifica?

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

enter image description here

R: Ambas tags siguen apuntando al mismo objeto, pero ese objeto en sí mismo ha mutado (su contenido ha cambiado para ser otra cosa).


¿Cómo se relaciona esto con la pregunta original?

Es básicamente lo mismo que sucede en Q3 / Q4; el método obtiene su propia copia privada de la variable / etiqueta ( str2 ) que se pasa a ella ( str ). No puede cambiar a qué objeto apunta la etiqueta str , pero puede cambiar el contenido del objeto al que ambos hacen referencia para contener 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 

¿Pasa Ruby por referencia o por valor?

Ruby es de paso por valor. Siempre. Sin excepciones. No, si. Sin peros.

Aquí hay un progtwig simple que demuestra ese hecho:

 def foo(bar) bar = 'reference' end baz = 'value' foo(baz) puts "Ruby is pass-by-#{baz}" # Ruby is pass-by-value 

Ruby usa “pasar por referencia de objeto”

(Usando la terminología de Python)

Decir que Ruby usa “pasar por valor” o “pasar por referencia” no es lo suficientemente descriptivo como para ser útil. Creo que como la mayoría de la gente lo sabe en estos días, esa terminología (“valor” frente a “referencia”) proviene de C ++.

En C ++, “pasar por valor” significa que la función obtiene una copia de la variable y cualquier cambio en la copia no cambia el original. Eso es cierto para los objetos también. Si pasa una variable de objeto por valor, se copiará todo el objeto (incluidos todos sus miembros) y cualquier cambio en los miembros no cambiará esos miembros en el objeto original. (Es diferente si pasas un puntero por valor pero Ruby no tiene punteros de todos modos, 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; ax = 5; inc(a); printf("in main: %d\n", ax); // => 5 A* b = new A; b->x = 0; inc(b); printf("in main: %d\n", b->x); // => 1 return 0; } 

Salida:

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

En C ++, “pasar por referencia” significa que la función tiene acceso a la variable original. Puede asignar un entero literal nuevo y la variable original también tendrá ese valor.

 void replace(A &arg) { A newA; newA.x = 10; arg = newA; printf("in replace: %d\n", arg.x); } int main() { A a; ax = 5; replace(a); printf("in main: %d\n", ax); return 0; } 

Salida:

 in replace: 10 in main: 10 

Ruby usa pasar por valor (en el sentido C ++) si el argumento no es un objeto. Pero en Ruby todo es un objeto, por lo que realmente no hay pase por valor en el sentido de C ++ en Ruby.

En Ruby, se usa “pass by object reference” (para usar la terminología de Python):

  • Dentro de la función, cualquiera de los miembros del objeto puede tener nuevos valores asignados y estos cambios persistirán después de que la función regrese. *
  • Dentro de la función, la asignación de un objeto completamente nuevo a la variable hace que la variable deje de hacer referencia al objeto anterior. Pero después de que la función retorna, la variable original seguirá haciendo referencia al objeto viejo.

Por lo tanto, Ruby no usa “pasar por referencia” en el sentido C ++. Si así fuera, asignar un nuevo objeto a una variable dentro de una función haría que el objeto viejo se olvidara después de que se devolviera la función.

 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 ax = 1 puts ax # 1 inc a # 2 puts ax # 2 replace a # 3 puts ax # 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 

Salida:

 1 2 2 3 2 1 2 1 

* Esta es la razón por la cual, en Ruby, si desea modificar un objeto dentro de una función pero olvida esos cambios cuando la función retorna, debe hacer una copia explícita del objeto antes de realizar los cambios temporales en la copia.

Ya hay algunas buenas respuestas, pero quiero publicar la definición de un par de autoridades sobre el tema, pero también espero que alguien pueda explicar lo que dijeron las autoridades de Matz (creador de Ruby) y David Flanagan en su excelente libro O’Reilly, El lenguaje de progtwigción Ruby .

[de 3.8.1: Referencias del objeto]

Cuando pasa un objeto a un método en Ruby, es una referencia de objeto que se pasa al método. No es el objeto en sí mismo, y no es una referencia a la referencia al objeto. Otra forma de decir esto es que los argumentos del método se pasan por valor en lugar de por referencia , pero que los valores pasados ​​son referencias a objetos.

Como las referencias a objetos se pasan a los métodos, los métodos pueden usar esas referencias para modificar el objeto subyacente. Estas modificaciones son visibles cuando el método regresa.

Todo esto tiene sentido para mí hasta el último párrafo, y especialmente esa última oración. Esto es, en el mejor de los casos, engañoso y, en el peor de los casos, confunde. ¿De qué manera, de alguna manera, las modificaciones a esa referencia pasada por valor pueden cambiar el objeto subyacente?

¿Pasa Ruby por referencia o por valor?

Ruby es de pasada por referencia. Siempre. Sin excepciones. No, si. Sin peros.

Aquí hay un progtwig simple que demuestra ese hecho:

 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 pasa por referencia 2279146940 porque los object_id (direcciones de memoria) son siempre los mismos;)

 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}" 

=> algunas personas no se dan cuenta de que es una referencia porque la asignación local puede tener prioridad, pero es claramente una referencia de paso a paso

Ruby es pass-by-value en un sentido estricto, PERO los valores son referencias.

Esto podría llamarse ” pass-reference-by-value “. Este artículo tiene la mejor explicación que he leído: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

Pass-reference-by-value podría explicarse brevemente de la siguiente manera:

Una función recibe una referencia a (y accederá) al mismo objeto en memoria utilizado por la persona que llama. Sin embargo, no recibe el cuadro en el que la persona que llama está almacenando este objeto; como en pass-value-by-value, la función proporciona su propio cuadro y crea una nueva variable para sí mismo.

El comportamiento resultante es en realidad una combinación de las definiciones clásicas de pass-by-reference y pass-by-value.

Los parámetros son una copia de la referencia original. Por lo tanto, puede cambiar los valores, pero no puede cambiar la referencia original.

Ruby es interpretado. Las variables son referencias a datos, pero no a los datos en sí. Esto facilita el uso de la misma variable para datos de diferentes tipos.

La asignación de lhs = rhs luego copia la referencia en el rhs, no los datos. Esto difiere en otros lenguajes, como C, donde la asignación hace una copia de datos a lhs desde rhs.

Entonces, para la llamada de función, la variable pasada, digamos x, de hecho se copia en una variable local en la función, pero x es una referencia. Luego habrá dos copias de la referencia, ambas haciendo referencia a los mismos datos. Uno estará en la persona que llama, uno en la función.

La asignación en la función copiaría una nueva referencia a la versión de la función de x. Después de esto, la versión de la persona que llama de x permanece inalterada. Sigue siendo una referencia a los datos originales.

En contraste, usar el método .replace en x hará que ruby ​​haga una copia de datos. Si se usa replace antes de cualquier asignación nueva, entonces la persona que llama verá que los datos también cambian en su versión.

De forma similar, siempre que la referencia original esté en contacto con la variable pasada, las variables de instancia serán las mismas que ve la persona que llama. Dentro del marco de un objeto, las variables de instancia siempre tienen los valores de referencia más actualizados, ya sean proporcionados por el que llama o configurados en la función a la que se le pasó la clase.

La ‘llamada por valor’ o ‘llamada por referencia’ se confunde aquí debido a la confusión sobre ‘=’ En los lenguajes comstackdos ‘=’ es una copia de datos. Aquí en este lenguaje interpretado ‘=’ es una copia de referencia. En el ejemplo, tiene la referencia pasada, seguida por una copia de referencia, aunque ‘=’, que anula el original pasado en referencia, y luego la gente habla de ello como si ‘=’ fuera una copia de datos.

Para ser coherente con las definiciones, debemos mantenernos con ‘.replace’ ya que es una copia de datos. Desde la perspectiva de ‘.replace’, vemos que esto es, de hecho, pasa por referencia. Además, si revisamos el depurador, vemos referencias que se pasan, ya que las variables son referencias.

Sin embargo, si debemos mantener ‘=’ como un marco de referencia, entonces sí podemos ver los datos pasados ​​hasta una asignación, y luego no lo vemos más después de la asignación mientras los datos de la persona que llama permanecen sin cambios. A nivel conductual, esto pasa por valor siempre que no consideremos que el valor pasado sea compuesto, ya que no podremos mantener parte de él mientras cambiamos la otra parte en una sola asignación (como esa asignación). cambia la referencia y el original sale del scope). También habrá una verruga, en ese caso las variables en los objetos serán referencias, como todas las variables. Por lo tanto, nos veremos obligados a hablar sobre pasar ‘referencias por valor’ y tener que usar locuciones relacionadas.

Prueba esto:–

 1.object_id #=> 3 2.object_id #=> 5 a = 1 #=> 1 a.object_id #=> 3 b = 2 #=> 2 b.object_id #=> 5 

el identificador a contiene el object_id 3 para el objeto de valor 1 y el identificador b contiene el object_id 5 para el objeto de valor 2.

Ahora hacer esto:–

 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 

Ahora, a y b contienen el mismo objeto_id 5 que se refiere al objeto de valor 2. Entonces, la variable de Ruby contiene object_ids para referirse a los objetos de valor.

Hacer lo siguiente también da error:

 c #=> error 

pero hacer esto no dará error:

 5.object_id #=> 11 c = 5 #=> value object 5 provides return type for variable c and saves 5.object_id ie 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 

Aquí el identificador a devuelve el objeto de valor 11 cuyo identificador de objeto es 23, es decir, el identificador de objeto 23 está en el identificador a, Ahora vemos un ejemplo utilizando el método.

 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 in foo se asigna con el valor de retorno de x. Muestra claramente que el argumento se pasa por el valor 11, y el valor 11 siendo él mismo un objeto tiene el id único de objeto 23.

Ahora mira esto también:

 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 

Aquí, el identificador arg primero contiene object_id 23 para referirse a 11 y después de la asignación interna con el objeto de valor 12, contiene object_id 25. Pero no cambia el valor al que hace referencia el identificador x utilizado en el método de llamada.

Por lo tanto, Ruby pasa por valor y las variables de Ruby no contienen valores pero sí contienen referencia al objeto de valor.

Debe tenerse en cuenta que ni siquiera tiene que usar el método “reemplazar” para cambiar el valor del valor original. Si asigna uno de los valores hash para un hash, está cambiando el valor original.

 def my_foo(a_hash) a_hash["test"]="reference" end; hash = {"test"=>"value"} my_foo(hash) puts "Ruby is pass-by-#{hash["test"]}" 
 Two references refer to same object as long as there is no reassignment. 

Cualquier actualización en el mismo objeto no hará referencia a la nueva memoria ya que aún está en la misma memoria. Aquí hay algunos ejemplos:

  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"}}