web-dev-qa-db-fra.com

Ruby est-il passé par référence ou par valeur?

@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?

L'objet @user ajoute des erreurs à la variable lang_errors de la méthode update_lanugages. Lorsque j'effectue une sauvegarde sur l'objet @user, je perds les erreurs initialement stockées dans la variable lang_errors.

Bien que ce que je tente de faire serait plutôt un bidouillage (qui ne semble pas fonctionner). J'aimerais comprendre pourquoi les valeurs variables sont effacées. Je comprends passe par référence, alors j'aimerais savoir comment la valeur peut être conservée dans cette variable sans être effacée.

241
Sid

Dans la terminologie traditionnelle, Ruby est strictement pass-by-value . Mais ce n'est pas vraiment ce que vous demandez ici.

Ruby n'a aucun concept de valeur pure et non référencée, vous ne pouvez donc certainement pas en transmettre une à une méthode. Les variables sont toujours des références à des objets. Pour obtenir un objet qui ne changera pas, vous devez dupliquer ou dupliquer l'objet que vous avez transmis, donnant ainsi un objet auquel personne d'autre ne fait référence. (Même si cela n’est pas à l'épreuve des balles, les deux méthodes de clonage standard font une copie superficielle. Les variables d'instance du clone pointent toujours sur les mêmes objets que les originaux. Si les objets référencés par les ivars muent, cela toujours apparaître dans la copie, car il fait référence aux mêmes objets.)

235
Chuck

Les autres répondants ont tous raison, mais un ami m'a demandé de lui expliquer cela. En résumé, voici comment Ruby gère les variables. J'ai donc pensé partager quelques images/explications simples pour lesquelles j'ai écrit. lui (excuses pour la longueur et probablement une simplification excessive):


Q1: Que se passe-t-il lorsque vous affectez une nouvelle variable str à une valeur de 'foo'?

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

enter image description here

R: Une étiquette appelée str est créée et pointe sur l'objet 'foo', qui, pour l'état de cet interprète Ruby, se trouve à l'emplacement de mémoire 2000.


Q2: Que se passe-t-il lorsque vous affectez la variable existante str à un nouvel objet à l'aide de =?

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

enter image description here

A: L'étiquette str pointe maintenant vers un objet différent.


Q3: Que se passe-t-il lorsque vous affectez une nouvelle variable = à str?

str2 = str
str2.object_id # => 2002

enter image description here

R: Une nouvelle étiquette appelée str2 est créée et pointe vers le même objet comme str.


Q4: Que se passe-t-il si l'objet référencé par str et str2 est modifié?

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

enter image description here

R: Les deux étiquettes pointent toujours sur le même objet, mais cet objet lui-même a muté (son contenu a changé pour être autre chose).


Comment cela se rapporte-t-il à la question initiale?

C'est fondamentalement la même chose que ce qui se passe au T3/T4; la méthode obtient sa propre copie privée de la variable/label (str2) qui lui est transmise (str). Il ne peut pas changer l'objet de l'étiquette strpointe vers, mais il peut changer le contenu de l'objet auquel ils font référence par autre chose:

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
415
Abe Voelker

Ruby est-il passé par référence ou par valeur?

Ruby est passe-par-valeur. Toujours. Aucune exception. Pas de si. Pas de mais.

Voici un programme simple qui démontre ce fait:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
45
Jörg W Mittag

Ruby utilise "passer par référence d'objet"

(Utilisation de la terminologie Python.)

Dire Ruby utilise "passe par valeur" ou "passe par référence" n'est pas assez descriptif pour être utile. Comme la plupart des gens le savent de nos jours, je pense que la terminologie ("valeur" ou "référence") vient du C++.

En C++, "transmission par valeur" signifie que la fonction obtient une copie de la variable et que toute modification apportée à la copie ne modifie pas l'original. C'est vrai pour les objets aussi. Si vous transmettez une variable d'objet par valeur, tout l'objet (y compris tous ses membres) est copié et toute modification apportée aux membres ne modifie pas ces membres sur l'objet d'origine. (C'est différent si vous passez un pointeur par valeur mais que Ruby n'a pas de pointeur, de toute façon, autant que je sache.)

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

Sortie:

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

En C++, "passer par référence" signifie que la fonction a accès à la variable d'origine. Il peut assigner un tout nouveau nombre entier littéral et la variable d'origine aura alors aussi cette valeur.

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

Sortie:

in replace: 10
in main: 10

Ruby utilise passe par valeur (au sens de C++) si l'argument n'est pas un objet. Mais dans Ruby tout est un objet, il n'y a donc pas de valeur de passage pour la valeur au sens C++ de Ruby.

En Ruby, "référence par objet" (pour utiliser la terminologie de Python) est utilisé:

  • Dans la fonction, de nouvelles valeurs peuvent être attribuées à tous les membres de l'objet. Ces modifications sont conservées après le retour de la fonction. *
  • Dans la fonction, l'affectation d'un nouvel objet entier à la variable empêche celle-ci de faire référence à l'ancien objet. Mais après le retour de la fonction, la variable d'origine fera toujours référence à l'ancien objet.

Par conséquent, Ruby n'utilise pas "passe par référence" au sens C++. Si tel était le cas, l'affectation d'un nouvel objet à une variable dans une fonction entraînerait l'oubli de l'ancien objet après le retour de la fonction.

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

Sortie:

1
2
2
3
2

1
2
1

* C’est pourquoi, en Ruby, si vous souhaitez modifier un objet dans une fonction tout en oubliant ces modifications lorsque la fonction est renvoyée, vous devez explicitement copier l’objet avant de modifier temporairement la copie.

44
David Winiecki

Ruby est passe-par-valeur au sens strict, MAIS les valeurs sont des références.

Cela pourrait être appelé " référence-de-pass-par-valeur ". Cet article a la meilleure explication que j'ai lue: http://robertheaton.com/2014/07/22/is-Ruby-pass-by-reference-or-pass-by-value/

Pass-reference-by-value pourrait être brièvement expliqué comme suit:

Une fonction reçoit une référence (et accédera) au même objet en mémoire que celui utilisé par l'appelant. Cependant, il ne reçoit pas la boîte dans laquelle l'appelant stocke cet objet; comme dans pass-value-by-value, la fonction fournit sa propre boîte et crée une nouvelle variable pour elle-même.

Le comportement qui en résulte est en fait une combinaison des définitions classiques du passage par référence et du passage par valeur.

19
Ari

Il y a déjà de bonnes réponses, mais je souhaite publier la définition d'une paire d'autorités sur le sujet, mais j'espère aussi que quelqu'un pourrait expliquer ce que les autorités, Matz (créateur de Ruby) et David Flanagan, voulaient dire dans leur excellent livre O'Reilly, Le Ruby Langage de programmation.

[de 3.8.1: Références d'objet]

Lorsque vous transmettez un objet à une méthode en Ruby, il s'agit d'une référence d'objet transmise à la méthode. Ce n'est pas l'objet lui-même et ce n'est pas une référence à la référence à l'objet. Une autre façon de dire cela est que les arguments de la méthode sont passés par valeur plutôt que par par référence , mais que les valeurs transmises sont des références d'objet.

Comme les références d'objet sont transmises aux méthodes, celles-ci peuvent utiliser ces références pour modifier l'objet sous-jacent. Ces modifications sont alors visibles au retour de la méthode.

Tout cela a du sens pour moi jusqu'au dernier paragraphe, et en particulier cette dernière phrase. Cela est au mieux trompeur et au pire déconcertant. Comment, de quelque manière que ce soit, les modifications apportées à cette référence passée par valeur peuvent-elles changer l'objet sous-jacent?

16
Dominick

Ruby est-il passé par référence ou par valeur?

Ruby est passé par référence. Toujours. Aucune exception. Pas de si. Pas de mais.

Voici un programme simple qui démontre ce fait:

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 est un renvoi 2279146940 car les identifiants d'objet (adresses mémoire) sont toujours les mêmes;)

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

=> certaines personnes ne réalisent pas que c'est une référence parce que l'affectation locale peut avoir la priorité, mais c'est clairement passé par référence

15
Brett Allred

Les paramètres sont une copie de la référence d'origine. Ainsi, vous pouvez modifier les valeurs, mais ne pouvez pas modifier la référence d'origine.

8

Essaye ça:--

1.object_id
#=> 3

2.object_id
#=> 5

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

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

identifiant a contient object_id 3 pour l'objet de valeur 1 et identifiant b contient object_id 5 pour l'objet de valeur 2.

Maintenant, fais ceci: -

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

Maintenant, a et b contiennent tous deux le même identifiant d'objet_id 5 qui fait référence à l'objet de valeur 2. Ainsi, la variable Ruby contient les identifiants d'objet pour désigner des objets de valeur.

Faire ce qui suit donne aussi une erreur: -

c
#=> error

mais cela ne donnera pas d'erreur: -

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

Ici, l’identifiant a renvoie un objet de valeur 11 dont l’ID d’objet est 23, c’est-à-dire que id_objet 23 est identique à identifiant a. Nous voyons maintenant un exemple en utilisant method.

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 dans foo est assigné avec la valeur de retour de x. Il montre clairement que l'argument est passé par la valeur 11 et que la valeur 11 étant elle-même un objet, l'identifiant d'objet unique 23 est unique.

Maintenant, voyez aussi ceci: -

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

Ici, l'identifiant arg contient d'abord object_id 23 pour se référer à 11 et après affectation interne avec l'objet de valeur 12, il contient object_id 25. Mais il ne modifie pas la valeur référencée par l'identifiant x utilisé dans la méthode d'appel.

Par conséquent, Ruby est transmis par valeur et les variables Ruby ne contiennent pas de valeurs, mais contiennent une référence à un objet value.

1
Alok Anand

Ruby est interprété. Les variables sont des références à des données, mais pas les données elles-mêmes. Cela facilite l'utilisation de la même variable pour des données de types différents.

L'affectation de lhs = rhs copie alors la référence sur le rhs, pas les données. Cela diffère dans d'autres langues, telles que C, où l'affectation effectue une copie de données de lhs à partir de rhs.

Donc, pour l'appel de fonction, la variable passée, disons x, est effectivement copiée dans une variable locale de la fonction, mais x est une référence. Il y aura alors deux copies de la référence, faisant toutes deux référence aux mêmes données. L'un sera dans l'appelant, l'autre dans la fonction.

L'affectation dans la fonction copierait alors une nouvelle référence dans la version de x de la fonction. Après cela, la version de l'appelant de x reste inchangée. C'est toujours une référence aux données d'origine.

En revanche, si vous utilisez la méthode .replace sur x, Ruby effectuera une copie des données. Si remplacer est utilisé avant toute nouvelle affectation, l'appelant verra également les données changer dans sa version.

De la même manière, tant que la référence d'origine est intacte pour la variable passée, les variables d'instance seront les mêmes que celles que voit l'appelant. Dans le cadre d'un objet, les variables d'instance ont toujours les valeurs de référence les plus récentes, qu'elles soient fournies par l'appelant ou définies dans la fonction à laquelle la classe a été transmise.

Le 'appel par valeur' ​​ou 'appel par référence' est confondu ici à cause d'une confusion sur '=' Dans les langages compilés '=' est une copie de données. Ici, dans ce langage interprété '=' est une copie de référence. Dans l'exemple, vous avez la référence passée suivie d'une copie de référence bien que "=" qui entrave l'original passé en référence, puis les personnes qui en parlent comme si "=" était une copie de données.

Pour être cohérent avec les définitions, nous devons garder avec '.replace' car c'est une copie de données. Du point de vue de '.replace', nous voyons qu'il s'agit bien d'un renvoi à un autre. De plus, si nous parcourons le débogueur, nous voyons des références passées, car les variables sont des références.

Cependant, si nous devons conserver le "=" comme cadre de référence, nous verrons bien les données transmises jusqu'à l'attribution, puis nous ne le verrons plus après l'attribution tant que les données de l'appelant resteront inchangées. Sur le plan comportemental, il s’agit de transmission valeur par valeur tant que nous ne considérons pas que la valeur transmise est composite. En effet, nous ne pourrons pas en conserver une partie tout en modifiant l’autre partie dans une seule affectation change la référence et l'original sort du cadre). Il y aura également une verrue, en ce que les variables d'instance dans les objets seront des références, de même que toutes les variables. Par conséquent, nous serons obligés de parler de transmission de 'références par valeur' ​​et d'utiliser des locutions associées.

1
user3446498

Il est à noter qu'il n'est même pas nécessaire d'utiliser la méthode "replace" pour modifier la valeur d'origine. Si vous affectez l'une des valeurs de hachage à un hachage, vous modifiez la valeur d'origine.

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

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

Toute mise à jour dans le même objet ne fera pas les références à une nouvelle mémoire car il est toujours dans la même mémoire. Voici quelques exemples:

    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
Ayman Hussain