¿Ruby pass es 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 object agrega errores a la variable lang_errors en el método update_lanugages. cuando realizo un guardado en el objeto @user pierdo los errores que se almacenaron inicialmente en la variable lang_errors.

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

Author: isomorphismes, 2009-12-09

12 answers

En terminología tradicional, Ruby es estrictamente pass-by-value. Pero eso no es realmente lo que estás preguntando aquí.

Ruby no tiene ningún concepto de un valor puro, sin referencia, por lo que ciertamente no se puede pasar uno a un método. Las variables son siempre referencias a objetos. Con el fin de obtener un objeto que no va a cambiar de debajo de usted, usted necesita para dup o clonar el objeto que se le pasa, dando así un objeto que nadie más tiene una referencia a. (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 todavía apuntan a los mismos objetos que los originales. Si los objetos referenciados por el ivars mutan, eso seguirá apareciendo en la copia, ya que hace referencia a los mismos objetos.)

 221
Author: Chuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:29

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 fotos / explicaciones simples que escribí para él (disculpas por la longitud y probablemente alguna simplificación excesiva):


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

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

introduzca la descripción de la imagen aquí

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


P2: ¿Qué sucede cuando se 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

introduzca la descripción de la imagen aquí

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


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

str2 = str
str2.object_id # => 2002

introduzca la descripción de la imagen aquí

A: Se crea una nueva etiqueta llamada str2 que apunta a la misma objeto como str.


P4: ¿Qué sucede si el objeto referenciado por str y str2 se cambia?

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

introduzca la descripción de la imagen aquí

R: Ambas etiquetas todavía apuntan 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 / label (str2) que se pasa a ella (str). No puede cambiar el objeto de la etiqueta str apunta a , pero puede cambiar el contenido de el 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
 398
Author: Abe Voelker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-11 03:03:24

¿Ruby pass es por referencia o por valor?

Ruby es paso por valor. Siempre. Excepción. Sin ifs. Sin peros.

Aquí hay un programa 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
 45
Author: Jörg W Mittag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-26 09:22:23

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 "vs" 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 entonces el objeto entero (incluyendo todos sus miembros) consigue copiado y cualquier cambio a los miembros no cambia 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;
  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;
}

Salida:

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

En C++, "pasar por referencia" significa que la función obtiene acceso a la variable original. Puede asignar un entero literal nuevo y la variable original tendrá ese valor demasiado.

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

Salida:

in replace: 10
in main: 10

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

En Ruby, se usa" pasar por referencia de objeto " (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, asignar un objeto nuevo completo a la variable hace que la variable deje de hacer referencia al objeto antiguo. Pero después de que la función regrese, la variable original seguirá haciendo referencia al objeto antiguo.

Por lo tanto Ruby no usa "pass by reference" en el sentido de C++. Si lo hiciera, entonces asignar un nuevo objeto a una variable dentro de una función haría que el objeto antiguo se olvidara después de que la función regresara.

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

Salida:

1
2
2
3
2

1
2
1

* Por eso, en Ruby, si desea modificar un objeto dentro de una función pero olvida esos cambios cuando la función regresa, entonces debe hacer explícitamente una copia del objeto antes de realizar sus cambios temporales en la copia.

 37
Author: David Winiecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-17 15:54:26

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

[de 3.8.1: Referencias de objetos]

Cuando se 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.

Debido a que las referencias de 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 ese último párrafo, yespecialmente esa última frase. Esto es, en el mejor de los casos, engañoso y, en el peor, confuso. ¿Cómo, de alguna manera, podrían las modificaciones a esa referencia pasada por valor cambiar el objeto subyacente?

 16
Author: Dominick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-05 22:42:24

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 (y accederá) al mismo objeto en memoria que el que llama. Sin embargo, no recibe el cuadro en el que el llamante está almacenando este objeto; como en pass-value-by-value, la función proporciona su propio cuadro y crea una nueva variable para sí misma.

El comportamiento resultante es en realidad una combinación de las definiciones clásicas de paso por referencia y paso por valor.

 15
Author: Ari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-28 16:36:00

¿Ruby pass es por referencia o por valor?

Ruby es paso por referencia. Siempre. Excepción. Sin ifs. Sin peros.

Aquí hay un programa 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 es la referencia de paso 2279146940 porque las direcciones de memoria de object_id son siempre las mismas;)

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 referencia de paso

 14
Author: Brett Allred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-17 23:03:48

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

 8
Author: Rael Gugelmin Cunha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-21 16:06:46

Se interpreta Ruby. 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.

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.

Así que para la llamada a la función, la variable pasada, digamos x, se copia en una variable local en la función, pero x es una referencia. Habrá luego ser dos copias de la referencia, ambas haciendo referencia a los mismos datos. Uno estará en el llamador, uno en la función.

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

En contraste, usando el .reemplazar el método en x hará que ruby haga una copia de datos. Si se utiliza reemplazar antes de cualquier nueva asignación, entonces de hecho el llamante verá la cambio de datos en su versión también.

Del mismo modo, siempre y cuando la referencia original esté intacta para la variable passed in, las variables de instancia serán las mismas que ve el llamante. Dentro del marco de un objeto, las variables de instancia siempre tienen los valores de referencia más actualizados, ya sean proporcionados por el llamante o establecidos en la función a la que se pasó la clase.

La llamada por valor' o 'llamada por referencia" es confusa debido a la confusión sobre '=' En idiomas compilados ' = ' es una copia de datos. Aquí en este idioma interpretado ' = ' es una copia de referencia. En el ejemplo tienes la referencia pasada seguida por una copia de referencia aunque ' = 'que clobbers el original pasado en referencia, y luego la gente hablando de ella como si' = ' eran una copia de datos.

Para ser coherentes con las definiciones debemos mantener".sustitúyase" ya que es una copia de datos. Desde la perspectiva de '.sustitúyase " vemos que esto es de hecho pasar por referencia. Además, si caminamos a través del depurador, vemos referencias que se pasan, ya que las variables son referencias.

Sin embargo, si debemos mantener '=' como un marco de referencia, entonces de hecho podemos ver los datos pasados hasta una asignación, y luego no podemos verlos más después de la asignación mientras los datos de la persona que llama permanecen sin cambios. En un nivel de comportamiento, esto es pasar por valor siempre y cuando no consideremos que el valor pasado sea compuesto, ya que no podremos mantener parte de él mientras cambiar la otra parte en una sola asignación (ya que esa asignación cambia la referencia y el original sale del ámbito). También habrá una verruga, en esa instancia las variables en los objetos serán referencias, al igual que todas las variables. Por lo tanto, nos veremos obligados a hablar de pasar 'referencias por valor' y tendremos que usar locuciones relacionadas.

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-21 13:01:30

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 object_id 3 para el objeto de valor 1 y el identificador b contiene object_id 5 para el objeto de valor 2.

Ahora haz 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 object_id 5 que se refiere al valor objeto 2. Por lo tanto, la variable Ruby contiene object_ids para referirse a 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 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

Aquí identificador a devuelve valor objeto 11 cuya id de objeto es 23 es decir, object_id 23 está en el identificador a, Ahora vemos un ejemplo mediante el uso de 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 en foo se asigna con el valor devuelto de x. Muestra claramente que el argumento es pasado por el valor 11, y el valor 11 siendo en sí mismo un objeto tiene un id de objeto único 23.

Ahora vea 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 valor object 12, contiene object_id 25. Pero no cambia el valor referenciado por el identificador x utilizado en el método de llamada.

Por lo tanto, Ruby es pass by value y las variables Ruby no contienen valores pero sí contienen referencia al objeto value.

 1
Author: Alok Anand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-25 06:03:53

Debe tenerse en cuenta que ni siquiera tiene que usar el método "reemplazar" para cambiar el valor original. Si asigna uno de los valores hash a 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"]}"
 1
Author: Don Carr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-28 03:11:59
Two references refer to same object as long as there is no reassignment. 

Cualquier actualización en el mismo objeto no hará las referencias a la nueva memoria, ya que todavía está en la misma memoria. Estos son 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"}}
 1
Author: Ayman Hussain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-20 20:09:37