mun icon indicating copy to clipboard operation
mun copied to clipboard

Pass argument by reference

Open Wodann opened this issue 5 years ago • 4 comments

Similar to C#, we want to use the ref keyword in function signatures and function calls to signal that we are passing an argument by reference.

NOTE: Do not confuse the concept of passing by reference with the concept of reference types (i.e. struct(gc). The two concepts are not the same. A function parameter can be modified by ref regardless of whether it is a value type or a reference type.

To use a ref parameter, both the method definition and the calling method must explicitly use the ref keyword, as shown in the following example:

// `ref` implies that the arg is mutable
fn foo(ref arg: u32) {
  arg += 2;
}

fn main() {
  // A variable needs to be mutable (`mut`) in order to bind a `ref` to it
  let mut value = 4;
  foo(ref value);
  assert_eq!(value, 6);
}

As opposed to C# we will not introduce the out keyword, as people can already return anonymous tuples from a function.

[discussion 1] Is there a use case for using ref without actually editing the value it refers to? If so, we should add ref mut?

[discussion 2] If we do demand ref mut, should the function call site also use ref mut?

Edit: updated the proposal example to remove ref mut. After discussion with @baszalmstra, we don't think there are any use cases where you would not want to edit the ref binding.

Wodann avatar May 22 '20 19:05 Wodann

[discussion 2] Writing mut ref has the added benefit that you are self documenting a mutable reference at the call site.

Wodann avatar May 22 '20 19:05 Wodann

Just to add my 2 cents: There are definitely cases where passing by reference do not imply mutation, like iterators or any aggregation;

p.s. why ref instead of Rust syntax (&) ?

fominok avatar May 22 '20 23:05 fominok

I think it's ref as in a reference to the variable itself rather than a reference type.

wackbyte avatar May 22 '20 23:05 wackbyte

The reason why we put ref in front of the variable instead of doing &u32, is because the ref does not apply to the value arg, but to the binding of arg. It works the same as Rust's ref pattern.

fn modify_something(ref a: i32) {
    a = 7;
    modify_something_else(mut a);
}

fn modify_something_else(mut a: i32) {
   a = 9
}

fn example() {
   let mut a = 4;
   modify_something(ref a);
   // The value of `a` is 7
}

Mun works differently compared to Rust, in that we have garbage collected structs (i.e struct Implicit or struct(gc) Explicit). Using the ref pattern allows us to modify the binding of a struct. E.g.:

struct(gc) Foo {
  a: u32,
  b: f32,
}

fn main() {
  let mut foo = Foo { a: 5, b: -3.14 };

  edit_foo_value(foo);
  assert_eq!(foo.a, 5);
  assert_eq!(foo.b, -3.14);

  edit_foo_ref(ref foo);
  assert_eq!(foo.a, 1);
  assert_eq!(foo.b, 6.28);
}

fn edit_foo_value(arg: Foo) {
  // Assigns a new GC pointer to the local variable `arg`
  // This will be garbage collected after returning from this function, as it is no longer used
  arg = Foo { a: 1, b: 6.28 }
}

fn edit_foo(ref arg: Foo) {
  // The external `arg` binding now points to a new GC pointer
  arg = Foo { a: 1, b: 6.28 }
}

Similarly, for struct(value) - which are stack-allocated - we can now modify the argument that we bind to:

struct(value) Foo {
  a: u32,
  b: f32,
}

fn main() {
  let mut foo = Foo { a: 5, b: -3.14 };

  edit_foo_value(foo);
  assert_eq!(foo.a, 5);
  assert_eq!(foo.b, -3.14);

  edit_foo_ref(ref foo);
  assert_eq!(foo.a, 1);
  assert_eq!(foo.b, 6.28);
}

fn edit_foo_value(arg: Foo) {
  arg.a = 1;
  arg.b = 6.28;
}

fn edit_foo_ref(ref arg: Foo) {
  arg.a = 1;
  arg.b = 6.28;
}

Wodann avatar May 23 '20 10:05 Wodann