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

3

21

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)
Eggcup answered 6/8, 2014 at 21:0 Comment(0)
N
18

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.

Nursery answered 6/8, 2014 at 22:10 Comment(4)
Thanks for this concise, complete answer! What I'm getting from your answer is that variables in Dart, et al. store a pointer to an object, but because that pointer is not guaranteed to point to a real or fixed place in memory, the programmer is not given access to the raw pointer. When passing-by-reference, the value of the reference is really being passed. In the case of unitizialized varibales, the null reference is passed and, especially with these anonymous callbacks, any new variable assignments are lost with the scope.Eggcup
So this is pass-by-value, not pass-by-reference. Look anywhere on Stack Overflow and you will see that Java is described as pass-by-value only, and does not have pass-by-reference. True pass-by-reference using & in C++/PHP or ref in C# allows you to assign (i.e. =) to the parameter variable in the function and have the same effect as assigning to the variable in the original scope, which is not the case here. So this is not pass-by-reference, and you should never refer to it as such.Wenzel
True… using pass-by-reference in this way is a frequent abuse of language, though. I will precise my answer to clarify that.Nursery
The null reference for single isn't the issue, exactly. Even initializing Egg single=Egg('unknown') doesn't fix this. Because the single egg isn't modifed; the cookOne is making a new Egg instance. The single variable isn't modified or replaced. To actually modify that egg, one needs to use a method to modify that egg. So, use () => egg.style="scrambled" Nonesuch
A
9

A popular misconception is that Dart is pass-by-reference. However, in a true pass-by-reference system (supported by languages such as C++), a function can set (and not just mutate) local variables in the caller.

Dart, like many other languages (e.g. Java, Python), is technically pass-by-value where the "value" of an object is a reference to it. This is why functions can mutate arguments passed by the caller.

However, in these languages where everything is an object and where there are not separate pointer types, "pass-by-value" and "pass-by-reference" can be confusing terms and are not particularly meaningful. A lot of Python programmers instead use the term pass-by-assignment. (This also sometimes is called call-by-sharing).

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

var x = 42;
var list = ['Hello world!'];

void foo(int intArgument, List<String> listArgument) {
  ...
}

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

then the behavior would be equivalent to:

var x = 42;
var list = ['Hello world!'];

void foo() {
  var intArgument = x;
  var listArgument = list;
  ...
}

void main() {
  foo();
}

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

Anile answered 17/4, 2020 at 22:9 Comment(0)
W
7

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.

Wenzel answered 7/8, 2014 at 9:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.