1
$\begingroup$

Background

What I want is to make the behaviors of the following two pieces of code identical:

specifier x = getX();
doSomething(x);
function f(specifier x) {
    doSomething(x);
}
f(getX());

That is, to make the behavior of passing a parameter identical to initializing a variable. (The initialization looks like an assignment. The relationship between it and assignment is not discussed here. But the distinction is important in the C++ example.)

Beside the type, the specifier could also specify whether x is a new variable located in an independent address, or an alias of the thing assigned to it, that is, passing by value or by reference. And I may extend it to support more options like the two.

C++ has done it right. Specifically, references could be defined in context other than passing a parameter, to imitate passing a parameter:

Type &x = getX();

Or in C#:

ref Type x = ref getX();

So there is nothing for me to invent. But while I was trying to build something else related to this distinction, I found a strange source of confusion, that some languages tries to make the opposite correspondence. An example is Pascal, where var means both creating variables in independent addresses, and making alias of the passed parameter:

var x, y : T;
begin
    x := y;
    doSomething(x); // y is not modified
end.
var y : T;
procedure f(var x : T);
begin
    doSomething(x); // y is modified
end;
begin
    f(y);
end.

Question

How does languages distinguish passing a parameter by value and reference? I'm especially interested in languages that are not like C++, that initializing a reference isn't so different from assignment, or doesn't have the concept of reference like in C++, so what works in C++ couldn't easily apply. I hope to see languages using the correspondence like in C++, but languages like Pascal or using other ways could also be helpful, as it may tell how the different ideas developed and how much it affects.

Beside the syntax, I'm also interested in how they are distinguished conceptually, in the specification or other materials such as textbooks. The problem of "passing by value or by reference" is it is too specific about parameters. It isn't easily understandable if I say creating a variable, or even creating a nameless value "by value". Languages using keywords might be a bit more helpful than languages using symbols as they automatically provide readable names, but that might not cover the tagless option.

$\endgroup$

1 Answer 1

1
$\begingroup$

Swift

Swift doesn't let you generally create aliases the way T& works in C++. To cause a parameter to be passed by reference, you mark it with inout in the function declaration and & at the call site:

func increment(_ x: inout Int) {
  x += 1
}

var y = 1
increment(&y)
print(y) // prints 2

Notably, the & prefix is not necessary if the function is an operator.

struct Foo {
  static func += (lhs: inout Foo, rhs: Foo) {
  }
}

var x = Foo(), y = Foo()
x += y // passed inout with no &

If you don't mark the parameter inout, not only can mutations not be seen by the caller, you can't even try to mutate it -- you don't get a copy to mutate like in C. If you need a copy to mutate for whatever reason, you have to explicitly copy it:

func illegal(x: Int) {
  x += 1 // won't compile
}

func okay(x: Int) {
  var x = x // explicit copy -- it's supposed to look weird
  x += 1 // okay, but the caller doesn't see this!
}

Besides inout, there are two other specifiers available as of Swift 5.9: borrowing (the default for most things) and consuming (the default for arguments to inits). For primitives like Int they don't mean anything; for classes and CoW structs they change where the refcounting operations happen; and for non-copyable types they control ownership: borrowing lets the new function access the value but not consume it (like &T in Rust), while consuming transfers ownership and makes the value inaccessible to the caller (like plain T in Rust).

Default values are not available for inout parameters -- unlike C++ with T&&, you can't take a reference to an rvalue -- leading to overload pairs like this:

extension Int {
  static func random<T: RandomNumberGenerator>(in range: Range<Int>, using generator: inout T) -> Int {
    // ...
  }

  static func random(in range: Range<Int>) -> Int {
    var g = SystemRandomNumberGenerator()
    return random(in: range, using: &g)
  }
}

Finally, there's a subtle but important rule known as the Law of Exclusivity: when a variable is passed inout, that can be the only concurrent access to that variable. If the same variable is mutated in one way and accessed in another at the same time, the Swift runtime panics.

var global = 1

func addGlobal(to x: inout Int) {
  x += global
}

addGlobal(to: &global) // crashes due to exclusivity violation

By extension, you can't pass the same variable inout twice: foo(&bar, &bar) is always ill-formed.

On the ABI level: public functions take inout values as pointers. If there is no pointer available (such as for computed properties), creates a temporary and passes pointer to that, then assigns the temporary back to the original after the callee returns. Private functions can use a nonstandard calling convention anyways -- sometimes the compiler passes only the properties you need rather than the whole struct, for example -- so ABI guarantees don't apply to them.

$\endgroup$
4
  • $\begingroup$ If inout parameters are pointers does that mean changes inside the function to the local variable will be immediately visible from within the function, via other aliases? E.g. if an inout parameter points to an object field, will a local change to the parameter be visible in the object field before the function returns? This wouldn't be the case for "copy in, copy out" semantics. $\endgroup$
    – kaya3
    Nov 5 at 16:46
  • $\begingroup$ @kaya3: For many tasks, best performance could be achieved by allowing a compiler to, at its convenience, use either a reference to the original object or a temporary object which is copied on entry and written back on exit. $\endgroup$
    – supercat
    Nov 5 at 19:45
  • $\begingroup$ @kaya3 I’d have to do some experimenting to verify how different statements in the same function work but I think that might be an exclusivity violation? It’s late now (and I’ve been having internet problems all weekend) but I’ll get back to you when I can, probably tomorrow. $\endgroup$
    – Bbrk24
    Nov 6 at 5:38
  • $\begingroup$ @kaya3 Confirmed, it's an exclusivity violation to try to even read them both in different statements in the same function. $\endgroup$
    – Bbrk24
    Nov 6 at 13:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .