web-dev-qa-db-ja.com

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変数に最初に保存されたエラーが失われます。

私がやろうとしていることは、ハックのようです(これは機能していないようです)。変数値が洗い流される理由を理解したいと思います。参照渡しを理解しているため、その変数に値をウォッシュアウトせずに保持する方法を知りたいと思います。

241
Sid

従来の用語では、 Rubyは厳密に値渡し です。しかし、それはあなたがここで求めていることではありません。

Rubyには純粋な非参照値の概念がないため、メソッドに渡すことはできません。変数は常にオブジェクトへの参照です。下から変化しないオブジェクトを取得するには、渡されたオブジェクトを複製または複製する必要があります。したがって、他の誰にも参照されていないオブジェクトを提供します。 (ただし、これは防弾ではありません。標準のクローン作成方法はどちらも浅いコピーを行うため、クローンのインスタンス変数は元のオブジェクトと同じオブジェクトを引き続き指します。同じオブジェクトを参照しているため、まだコピーに表示されます。)

235
Chuck

他の回答者はすべて正しいですが、友人が彼にこれを説明するように私に頼みました、そしてそれは本当にRubyが変数を処理する方法であるので、私が書いたいくつかの簡単な写真/説明を共有すると思いました彼(長さと恐らくいくつかの単純化に対する謝罪):


Q1:新しい変数str'foo'の値に割り当てるとどうなりますか?

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

enter image description here

A:オブジェクト'foo'を指すstrと呼ばれるラベルが作成されます。これは、このRubyインタープリターの状態に対して、たまたまメモリロケーション2000にあります。


Q2:=を使用して既存の変数strを新しいオブジェクトに割り当てるとどうなりますか?

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

enter image description here

A:ラベルstrは別のオブジェクトを指すようになりました。


Q3:新しい変数=strに割り当てるとどうなりますか?

str2 = str
str2.object_id # => 2002

enter image description here

A:str2という新しいラベルが作成され、同じオブジェクト as strを指します。


Q4:strおよびstr2によって参照されるオブジェクトが変更されるとどうなりますか?

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

enter image description here

A:両方のラベルはまだ同じオブジェクトを指していますが、そのオブジェクト自体は変化しています(その内容は他のものに変更されています)。


これは元の質問とどのように関連していますか?

基本的にはQ3/Q4で起こることと同じです。メソッドは、それに渡される変数/ラベル(str2)の独自のプライベートコピーを取得します(str)。どのオブジェクトのラベルstrpointsを変更することはできませんが、contents ofが両方を参照するオブジェクトを変更することができます。

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は参照渡しですか、値渡しですか?

Rubyは値渡しです。常に。例外なく。 ifsはありません。なし。

その事実を示す簡単なプログラムを次に示します。

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は「オブジェクト参照による受け渡し」を使用します

(Pythonの用語を使用します。)

Rubyが「値による受け渡し」または「参照による受け渡し」を使用していると言っても、実際に役立つほど説明的ではありません。最近ではほとんどの人が知っているように、その用語(「値」と「参照」)はC++に由来すると思います。

C++では、「値渡し」とは、関数が変数のコピーを取得することを意味し、コピーに変更を加えても元のファイルは変更されません。これはオブジェクトにも当てはまります。オブジェクト変数を値で渡すと、オブジェクト全体(そのすべてのメンバーを含む)がコピーされ、メンバーへの変更は元のオブジェクトのそれらのメンバーを変更しません。 (ポインタを値で渡す場合は異なりますが、Rubyにはポインタがありません、とにかく。)

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で関数内のオブジェクトを変更したいが、関数が戻ったときにそれらの変更を忘れる場合、コピーに一時的な変更を加える前にオブジェクトのコピーを明示的に作成する必要がある理由です。

44
David Winiecki

Rubyは厳密な意味での値渡しですが、値は参照です。

これは「pass-reference-by-value」と呼ばれます。この記事には、私が読んだ中で最も良い説明があります: http://robertheaton.com/2014/07/22/is-Ruby-pass-by-reference-or-pass-by-value/

値によるパス参照は、次のように簡単に説明できます。

関数は、呼び出し側が使用するのと同じメモリ内のオブジェクトへの参照を受け取ります(そしてアクセスします)。ただし、呼び出し元がこのオブジェクトを保存しているボックスは受け取りません。値ごとの受け渡しの場合と同様に、関数は独自のボックスを提供し、それ自体の新しい変数を作成します。

結果の動作は、実際には、参照渡しと値渡しの古典的な定義の組み合わせです。

19
Ari

すでにいくつかの素晴らしい答えがありますが、私はこの主題に関する一対の権威の定義を投稿したいと思います。 Rubyプログラミング言語

[3.8.1から:オブジェクト参照]

オブジェクトをRubyのメソッドに渡すとき、それはメソッドに渡されるオブジェクト参照です。オブジェクト自体ではなく、オブジェクトへの参照への参照でもありません。別の言い方をすると、メソッドの引数はby referenceではなくby by valueで渡されますが、渡される値はオブジェクト参照です。

オブジェクト参照はメソッドに渡されるため、メソッドはそれらの参照を使用して、基礎となるオブジェクトを変更できます。これらの変更は、メソッドが戻るときに表示されます。

これは、最後の段落まで特にその最後の文まで意味があります。これはせいぜい誤解を招くだけでなく、さらに悪いことに交絡です。どういうわけか、その値渡しの参照を変更すると、基になるオブジェクトがどのように変更されるのでしょうか?

16
Dominick

Rubyは参照渡しですか、値渡しですか?

Rubyは参照渡しです。常に。例外なく。 ifsはありません。なし。

その事実を示す簡単なプログラムを次に示します。

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は、object_id(メモリアドレス)が常に同じであるため、参照渡し2279146940です;)

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

=>一部の人々は、ローカル割り当てが優先される可能性があるため、それが参照であることを認識しませんが、それは明らかに参照渡しです

15
Brett Allred

パラメーターは、元の参照のコピーです。そのため、値を変更できますが、元の参照を変更することはできません。

8

これを試して: -

1.object_id
#=> 3

2.object_id
#=> 5

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

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

識別子aには値オブジェクト1のobject_id 3が含まれ、識別子bには値オブジェクト2のobject_id 5が含まれます。

今これを行う:-

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の両方に、値オブジェクト2を参照する同じobject_id 5が含まれています。したがって、Ruby変数には、値オブジェクトを参照するobject_idが含まれています。

次のこともエラーになります:-

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

ここで、識別子aは、オブジェクトIDが23である値オブジェクト11を返します。つまり、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のargには、戻り値xが割り当てられます。引数が値11で渡されること、および値11自体がオブジェクトであり、一意のオブジェクトID 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にはまず11を参照するobject_id 23が含まれ、値object 12での内部割り当て後、object_id 25が含まれます。ただし、メソッドの呼び出しで使用される識別子xによって参照される値は変更しません。

したがって、Rubyは値渡しであり、Ruby変数には値は含まれませんが、値オブジェクトへの参照が含まれます。

1
Alok Anand

Rubyが解釈されます。変数はデータへの参照ですが、データ自体ではありません。これにより、異なるタイプのデータに同じ変数を使用しやすくなります。

Lhs = rhsを割り当てると、データではなくrhsの参照がコピーされます。これは、割り当てがrhsからlhsにデータコピーを行うCなど、他の言語では異なります。

そのため、関数呼び出しの場合、渡された変数、たとえばxは実際に関数内のローカル変数にコピーされますが、xは参照です。参照の2つのコピーがあり、両方が同じデータを参照します。 1つは呼び出し側にあり、もう1つは関数にあります。

関数内の割り当ては、関数のバージョンのxへの新しい参照をコピーします。この後、呼び出し元のxのバージョンは変更されません。元のデータへの参照のままです。

対照的に、xで.replaceメソッドを使用すると、Rubyがデータコピーを実行します。新しい割り当ての前にreplaceを使用すると、実際に呼び出し元にはバージョンのデータの変更も表示されます。

同様に、渡された変数の元の参照がそのままである限り、インスタンス変数は呼び出し元が見るものと同じになります。オブジェクトのフレームワーク内では、インスタンス変数は、呼び出し元によって提供されるか、クラスが渡された関数で設定されるかに関係なく、常に最新の参照値を持ちます。

「値による呼び出し」または「参照による呼び出し」は、「=」をめぐる混乱のために混乱しています。コンパイルされた言語では、「=」はデータのコピーです。ここで、この解釈された言語では、「=」は参照コピーです。この例では、参照が渡された後に参照コピーが続き、「=」は参照で渡された元を上書きし、その後、「=」がデータコピーであるかのように話します。

定義と一貫性を保つために、データコピーであるため、 '。replace'を保持する必要があります。 「.replace」の観点から、これは実際に参照渡しであることがわかります。さらに、デバッガーをウォークスルーすると、変数が参照であるため、参照が渡されるのがわかります。

ただし、「=」を参照フレームとして保持する必要がある場合、実際には、割り当てられるまで渡されたデータを確認することができ、呼び出し元のデータが変更されていない間は割り当て後にそれを確認することはできません。動作レベルでは、渡された値が複合であると考えない限り、これは値渡しです-単一の割り当てで他の部分を変更しながらその部分を保持することができないため(その割り当てとして)参照を変更すると、元のオブジェクトは範囲外になります)。また、すべての変数と同様に、オブジェクトのインスタンス変数が参照になるという点で、いぼもあります。したがって、「値による参照」の受け渡しについて話すことを余儀なくされ、関連する説明を使用する必要があります。

1
user3446498

値を元の値に変更するために「置換」メソッドを使用する必要さえないことに注意してください。ハッシュにハッシュ値の1つを割り当てると、元の値が変更されます。

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. 

同じオブジェクト内の更新は、まだ同じメモリ内にあるため、新しいメモリへの参照を行いません。以下に例を示します。

    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