What is the true meaning of pass-by-reference in modern languages like Dart?

落爺英雄遲暮 提交于 2020-05-15 08:18:07

问题


Working with Futures in Dart, I've come across an interesting issue.

import 'dart:async';

class Egg {
  String style;
  Egg(this.style);
}

Future cookEggs(List<Egg> list) =>
  new Future(() =>
    ['omelette','over easy'].forEach((_) => list.add(new Egg(_)))
  );

Future cookOne(Egg egg) => new Future(() => egg = new Egg('scrambled'));

void main() {
  List<Egg> eggList = new List();
  Egg single;

  cookEggs(eggList).whenComplete(() => eggList.forEach((_) => print(_.style));
  cookOne(single).whenComplete(() => print(single.style));
}

The expected output is:

omelette
over easy
scrambled

The cookEggs function that gets the List<Eggs> works fine, but accessing the style property of single is unsuccessful and throws a NoSuchMethodError.

I first thought that this might have something to do with pass-by-reference, but I don't see why Dart would pass a List by reference but not an Egg. Now I'm thinking that the discrepancy may have something to do with the assignment (=) operator and the List.add() method. I'm stuck in that train of thought, and I don't know how to test my hypothesis.

Any thoughts?

The program's output and stack trace is show below:

omelette
over easy
Uncaught Error: The null object does not have a getter 'style'.

NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
Stack Trace: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#1      main.<anonymous closure> (file:///path/to/eggs.dart:20:51)
#2      _rootRun (dart:async/zone.dart:719)
#3      _RootZone.run (dart:async/zone.dart:862)
#4      _Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:540)
#5      _Future._propagateToListeners (dart:async/future_impl.dart:577)
#6      _Future._complete (dart:async/future_impl.dart:317)
#7      Future.Future.<anonymous closure> (dart:async/future.dart:118)
#8      _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11)
#9      _handleTimeout (dart:io/timer_impl.dart:292)
#10     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)


Unhandled exception:
The null object does not have a getter 'style'.

NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
#0      _rootHandleUncaughtError.<anonymous closure>.<anonymous closure> (dart:async/zone.dart:713)
#1      _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
#2      _asyncRunCallback (dart:async/schedule_microtask.dart:32)
#3      _asyncRunCallback (dart:async/schedule_microtask.dart:36)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:128)

回答1:


Quick answer: what gets passed to your functions cookEggs and cookOne are references to the objects, not to the variables (which would be real pass-by-reference).

The term pass-by-reference is often misused to mean pass-references-by-value: many languages only have pass-by-value semantics, where the values that are passed around are references (i.e. pointers, without the dangerous features). See Is Java "pass-by-reference" or "pass-by-value"?

In the case of cookEggs(eggList)

…the variable eggList contains a reference to a list of eggs. That reference was initially given to you by the expression new List(), and you're passing it to cookEggs after storing meanwhile in your variable eggList. Inside cookEggs, adding to the list works, because you passed a reference to an actual, existing list object.

In the case of cookOne(single)

…the variable single has only been declared, so it was implicitly initialized by the language runtime to the special reference null. Inside cookOne, you're replacing which reference is contained in egg; since single is a different variable, it still contains null, therefore the code fails when you try to use that.

To clarify

The pass-references-by-value behavior is common to a lot of modern languages (Smalltalk, Java, Ruby, Python…). When you pass an object, you're actually passing-by-value (therefore copying) the contents of your variable, which is a pointer to the object. You never control where objects really exist.

Those pointers are named references rather than pointers, because they are restricted to abstract away the memory layout: you can't know the address of an object, you can't peek at the bytes around an object, you can't even be sure that an object is stored at a fixed place in memory, or that it's stored in memory at all (one could implement object references as UUIDs or keys in a persistent database, as in Gemstone).

In contrast, with pass-by-reference, you'd conceptually pass the variable itself, not its contents. To implement pass-by-reference in a pass-by-value language, you would need to reify variables as ValueHolder objects that can be passed around and whose contents can be changed.




回答2:


That's because, like Java, Dart is always pass-by-value, and never pass-by-reference. The semantics of passing and assigning in Dart is the same as in Java. Look anywhere on StackOverflow about Java and pass by value to see why Java is described as always pass-by-value. Terms like pass-by-value and pass-by-reference should be used consistently across languages. So Dart should be described as always pass-by-value.

In actual "pass-by-reference", e.g. when a parameter is marked with & in C++ or PHP, or when a parameter is marked with ref or out in C#, simple assignment (i.e. with =) to that parameter variable inside the function will have the same effect as simple assignment (i.e. with =) to the original passed variable outside the function. This is not possible in Dart.




回答3:


A lot of Python programmers uses the term pass-by-assignment. That applies to a lot of other languages, such as Java or Dart, where everything is an object and there are no separate pointer types. In such languages, pass-by-value and pass-by-reference are not particularly meaningful; these languages are technically pass-by-value where the "value" of an object is a reference to it.

Pass-by-assignment means that arguments are passed in way that's equivalent to normal variable assignment. For example, if I had:

final int x = 42;
final String s = "hello world!";

void foo(int intArgument, String stringArgument) {
  ...
}

void main() {
  foo(x, s);
}

then the behavior would be equivalent to:

...

void foo() {
  int intArgument = x;
  String stringArgument = s;
  ...
}

void main() {
  foo();
}

When you do String stringArgument = s;, stringArgument and s are two separate variables referring to the same object. Reassigning stringArgument to something else won't change what s refers to. However, if you mutate that object in place, both stringArgument and s will refer to the now modified object.



来源:https://stackoverflow.com/questions/25170094/what-is-the-true-meaning-of-pass-by-reference-in-modern-languages-like-dart

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!