Ruby passa per riferimento o valore?

@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 @user aggiunge errori alla variabile lang_errors nel metodo update_lanugages . quando lang_errors un salvataggio sull’object @user , perdo gli errori inizialmente memorizzati nella variabile lang_errors .

Anche se quello che sto tentando di fare sarebbe più di un hack (che non sembra funzionare). Mi piacerebbe capire perché i valori delle variabili sono sbiaditi. Capisco passaggio per riferimento, quindi vorrei sapere come il valore può essere contenuto in quella variabile senza essere sbiadito.

Nella terminologia tradizionale, Ruby è strettamente pass-by-value . Ma non è proprio quello che stai chiedendo qui.

Ruby non ha alcun concetto di un valore puro, non di riferimento, quindi non puoi certamente passare a un metodo. Le variabili sono sempre riferimenti a oggetti. Per ottenere un object che non cambierà da sotto di te, devi duplicare o clonare l’object che hai passato, dando così un object a cui nessun altro ha un riferimento. (Anche questo non è a prova di proiettile, però – entrambi i metodi di clonazione standard fanno una copia superficiale, quindi le variabili di istanza del clone puntano ancora agli stessi oggetti che gli originali hanno fatto. appaiono ancora nella copia, poiché fa riferimento agli stessi oggetti).

Gli altri risponditori sono tutti corretti, ma un amico mi ha chiesto di spiegarglielo e in realtà si tratta di come Ruby gestisce le variabili, quindi ho pensato di condividere alcune semplici immagini / spiegazioni che ho scritto per lui (ci scusiamo per la lunghezza e probabilmente qualche semplificazione eccessiva):


Q1: Cosa succede quando assegni una nuova variabile str ad un valore di 'foo' ?

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

inserisci la descrizione dell'immagine qui

A: Viene creata un’etichetta chiamata str che punta sull’object 'foo' , che per lo stato di questo interprete Ruby si trova nella posizione di memoria 2000 .


Q2: Cosa succede quando assegni la variabile esistente str a un nuovo object usando = ?

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

inserisci la descrizione dell'immagine qui

A: L’etichetta str punta ora su un object diverso.


Q3: cosa succede quando assegni una nuova variabile = a str ?

 str2 = str str2.object_id # => 2002 

inserisci la descrizione dell'immagine qui

A: Viene creata una nuova etichetta chiamata str2 che punta allo stesso object di str .


Q4: Cosa succede se l’object referenziato da str e str2 viene modificato?

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

inserisci la descrizione dell'immagine qui

A: Entrambe le etichette puntano ancora allo stesso object, ma quell’object stesso è mutato (il suo contenuto è cambiato per essere qualcos’altro).


Come si collega alla domanda originale?

È fondamentalmente uguale a quello che succede in Q3 / Q4; il metodo ottiene la propria copia privata della variabile / etichetta ( str2 ) che viene passata a esso ( str ). Non può cambiare a quale object punta l’etichetta str , ma può cambiare il contenuto dell’object che entrambi fanno riferimento per contenere altro:

 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 passa per riferimento o valore?

Ruby è un valore pass-by. Sempre. Nessuna eccezione. No se. No ma

Ecco un semplice programma che dimostra questo fatto:

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

Ruby usa “passa per riferimento all’object”

(Utilizzando la terminologia di Python.)

Dire che Ruby usa “passa per valore” o “passa per riferimento” non è abbastanza descrittivo per essere utile. Penso che la maggior parte delle persone lo sappiano in questi giorni, che la terminologia (“valore” vs “riferimento”) deriva da C ++.

In C ++, “passa per valore” significa che la funzione ottiene una copia della variabile e qualsiasi modifica alla copia non cambia l’originale. Questo vale anche per gli oggetti. Se si passa una variabile object per valore, l’object intero (inclusi tutti i suoi membri) viene copiato e le eventuali modifiche apportate ai membri non cambiano tali membri sull’object originale. (È diverso se si passa un puntatore in base al valore ma Ruby non ha puntatori in ogni caso, 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; } 

Produzione:

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

In C ++, “passa per riferimento” indica che la funzione ottiene l’accesso alla variabile originale. Può assegnare un intero intero letterale intero e la variabile originale avrà anche quel valore.

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

Produzione:

 in replace: 10 in main: 10 

Ruby usa passaggio per valore (nel senso C ++) se l’argomento non è un object. Ma in Ruby tutto è un object, quindi non c’è davvero alcun passaggio di valore nel senso C ++ in Ruby.

In Ruby viene utilizzato “pass by object reference” (per usare la terminologia di Python):

  • All’interno della funzione, a qualsiasi membro dell’object possono essere assegnati nuovi valori e tali modifiche verranno mantenute dopo il ritorno della funzione. *
  • All’interno della funzione, l’assegnazione di un nuovo object alla variabile fa sì che la variabile smetta di fare riferimento al vecchio object. Ma dopo il ritorno della funzione, la variabile originale farà ancora riferimento al vecchio object.

Pertanto Ruby non usa “passa per riferimento” nel senso C ++. In tal caso, l’assegnazione di un nuovo object a una variabile all’interno di una funzione causerebbe la cancellazione del vecchio object dopo la funzione restituita.

 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 

Produzione:

 1 2 2 3 2 1 2 1 

* Questo è il motivo per cui, in Ruby, se si desidera modificare un object all’interno di una funzione ma si dimenticano quelle modifiche quando la funzione ritorna, è necessario eseguire una copia dell’object prima di apportare le modifiche temporanee alla copia.

Ci sono già alcune grandi risposte, ma voglio postare la definizione di una coppia di autorità sull’argomento, ma anche sperare che qualcuno possa spiegare cosa hanno detto le autorità Matz (creatore di Ruby) e David Flanagan nel loro eccellente libro O’Reilly, The Ruby Programming Language .

[dalla 3.8.1: Riferimenti agli oggetti]

Quando si passa un object a un metodo in Ruby, si tratta di un riferimento a un object che viene passato al metodo. Non è l’object stesso e non è un riferimento al riferimento all’object. Un altro modo per dire questo è che gli argomenti del metodo vengono passati per valore piuttosto che per riferimento , ma che i valori passati sono riferimenti a oggetti.

Poiché i riferimenti agli oggetti vengono passati ai metodi, i metodi possono utilizzare tali riferimenti per modificare l’object sottostante. Queste modifiche sono quindi visibili al ritorno del metodo.

Tutto ciò ha senso per me fino all’ultimo paragrafo, e specialmente quell’ultima frase. Questo è nel migliore dei casi fuorviante, e in peggio confusione. In che modo, in qualche modo, le modifiche a tale riferimento pass-by-value possono modificare l’object sottostante?

Ruby passa per riferimento o valore?

Ruby è un riferimento pass-by. Sempre. Nessuna eccezione. No se. No ma

Ecco un semplice programma che dimostra questo fatto:

 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 è pass-by-reference 2279146940 perché object_id (indirizzi di memoria) sono sempre gli stessi;)

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

=> alcune persone non si rendono conto che è un riferimento perché l’assegnazione locale può avere la precedenza, ma è chiaramente un riferimento pass-by

Ruby è pass-by-value in senso stretto, MA i valori sono riferimenti.

Questo potrebbe essere chiamato ” pass-reference-by-value “. Questo articolo ha la migliore spiegazione che ho letto: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

Pass-reference-by-value potrebbe essere brevemente spiegato come segue:

Una funzione riceve un riferimento a (e accederà) allo stesso object in memoria utilizzato dal chiamante. Tuttavia, non riceve la casella in cui il chiamante sta memorizzando questo object; come in pass-value-by-value, la funzione fornisce una propria casella e crea una nuova variabile per se stessa.

Il comportamento risultante è in realtà una combinazione delle definizioni classiche di pass-by-reference e pass-by-value.

I parametri sono una copia del riferimento originale. Pertanto, è ansible modificare i valori, ma non modificare il riferimento originale.

Ruby è interpretato. Le variabili sono riferimenti ai dati, ma non ai dati stessi. Ciò facilita l’utilizzo della stessa variabile per i dati di tipi diversi.

Assegnazione di lhs = rhs quindi copia il riferimento sul rhs, non sui dati. Ciò differisce in altre lingue, come C, dove l’assegnazione fa una copia di dati a lhs da rhs.

Quindi per la chiamata di funzione, la variabile passata, per esempio x, viene effettivamente copiata in una variabile locale nella funzione, ma x è un riferimento. Ci saranno quindi due copie del riferimento, entrambe che fanno riferimento agli stessi dati. Uno sarà nel chiamante, uno nella funzione.

L’assegnazione nella funzione dovrebbe quindi copiare un nuovo riferimento alla versione della funzione di x. Dopo questo la versione del chiamante di x rimane invariata. È ancora un riferimento ai dati originali.

Al contrario, l’uso del metodo .replace su x farà sì che Ruby faccia una copia dei dati. Se la sostituzione viene utilizzata prima di qualsiasi nuova assegnazione, in effetti il ​​chiamante vedrà anche i cambiamenti dei dati nella sua versione.

Allo stesso modo, finché il riferimento originale è intatto per la variabile passata, le variabili di istanza saranno le stesse visualizzate dal chiamante. Nell’ambito di un object, le variabili di istanza hanno sempre i valori di riferimento più aggiornati, indipendentemente dal fatto che siano forniti dal chiamante o impostati nella funzione a cui è stata inoltrata la class.

La ‘chiamata per valore’ o ‘chiamata per riferimento’ è confusa qui a causa della confusione su ‘=’ In lingue compilate ‘=’ è una copia di dati. Qui in questa lingua interpretata ‘=’ è una copia di riferimento. Nell’esempio si ha il riferimento passato seguito da una copia di riferimento anche se ‘=’ che ignora l’originale passato in riferimento, e quindi le persone ne parlano come se ‘=’ fosse una copia di dati.

Per essere coerenti con le definizioni, è necessario mantenere “.replace” dato che si tratta di una copia di dati. Dal punto di vista di “.replace” vediamo che questo è davvero passato per riferimento. Inoltre, se passiamo attraverso il debugger, vediamo i riferimenti passati, poiché le variabili sono riferimenti.

Tuttavia, se dobbiamo mantenere ‘=’ come un quadro di riferimento, allora in effetti possiamo vedere i dati passati fino a un incarico, e quindi non riusciamo a vederli più dopo l’assegnazione mentre i dati del chiamante rimangono invariati. A livello comportamentale, questo valore viene passato per il valore a patto che non consideriamo il valore passato come composito, in quanto non saremo in grado di mantenerne una parte mentre cambiamo l’altra parte in un singolo compito (come quella assegnazione cambia il riferimento e l’originale esce dallo scopo). Ci sarà anche una verruca, in quell’istanza le variabili negli oggetti saranno riferimenti, come tutte le variabili. Quindi saremo costretti a parlare di “riferimenti per valore” e dobbiamo usare le locuzioni correlate.

Prova questo:–

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

l’identificativo a contiene object_id 3 per l’object valore 1 e l’identificatore b contiene object_id 5 per l’object valore 2.

Ora fai questo: –

 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 

Ora, a e b contengono entrambi lo stesso object_id 5 che si riferisce al valore dell’object 2. Quindi, la variabile Ruby contiene object_id per fare riferimento a oggetti valore.

Fare seguito dà anche errore: –

 c #=> error 

ma fare questo non darà errore: –

 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 

Qui identificatore un object restituisce valore 11 il cui id object è 23 cioè object_id 23 è all’identificatore a, Ora vediamo un esempio usando il metodo.

 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 è assegnato con il valore di ritorno di x. Mostra chiaramente che l’argomento è passato per il valore 11, e il valore 11 essendo esso stesso un object ha un ID object unico 23.

Ora guarda anche questo: –

 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 

Qui, l’identificatore arg contiene prima object_id 23 per fare riferimento a 11 e dopo l’assegnazione interna con l’object valore 12, contiene object_id 25. Ma non modifica il valore a cui fa riferimento l’identificatore x utilizzato nel metodo di chiamata.

Quindi, Ruby è un valore passato e le variabili Ruby non contengono valori ma contengono riferimenti a oggetti valore.

È necessario notare che non è necessario utilizzare il metodo “replace” per modificare il valore originale del valore. Se assegni uno dei valori hash per un hash, stai modificando il valore originale.

 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. 

Qualsiasi aggiornamento nello stesso object non renderà i riferimenti alla nuova memoria poiché è ancora nella stessa memoria. Ecco alcuni esempi:

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