New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

borrow/ref: Not clear what's the difference between ref and & #390

Closed
kornelski opened this Issue Jan 11, 2015 · 22 comments

Comments

Projects
None yet
6 participants
@kornelski
Copy link

kornelski commented Jan 11, 2015

In other languages & is called a reference and borrowing looks very much like references, so to me & and ref seem like the same thing.

I'd appreciate an example that contrasts the two — why let Point { x: ref ref_to_x, y: _ } is OK, but let Point { x: & ref_to_x, y: _ } isn't?

@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 11, 2015

ref is used in pattern matching, so its used when you want to borrow
something in, for example, a match statement.

let a = Some("Hello!".to_string());
match a {
    Some(ref s) => { //s is an &String here
    }
    ...
}

match a {
    Some(s) => {
        // s is a String here, and therefore
        // is owned by the match
    }
    ...
}
@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 11, 2015

Why can't you use & to borrow in the match?

match a {
    Some(&s) => {}
}

?

e.g. in methods I can use &self instead of ref self.

@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 11, 2015

That, I don't know. It's not really a decision I agree with. I like that
style better, personally.

On Sun, Jan 11, 2015 at 11:27 AM, Kornel notifications@github.com wrote:

Why can't you use & to borrow in the match?

match a {Some(&s) => {}
}

?


Reply to this email directly or view it on GitHub
#390 (comment)
.

"Calling all. This is our last cry before our eternal silence."
"The lone and level sands stretch far and away"

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 11, 2015

I'm not trying to question Rust design decision here, just understand difference between ref and & — and I'm reporting that the example about references doesn't elaborate on that.

Whether that's just arbitrary, or quirky syntax to avoid ambiguity, or it has slightly different meaning, I'd like to know that, because I'm not sure when to use which and why rustc complains about &_-ptr or such when I use & instead of ref.

@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 11, 2015

I think the idea is that you destruct as you construct, like in a

let op = Some(a);

match op {
    Some(x) => x,
    None => ...
}

So if you type

match op {
    Some(&x) => // here you are dereferencing a pointer
        ...
    None => ...
}

On Jan 11, 2015 11:44 AM, "Kornel" notifications@github.com wrote:

I'm not trying to question Rust design decision here, just understand
difference between ref and & — and I'm reporting that the example about
references doesn't elaborate on that.

Whether that's just arbitrary, or quirky syntax to avoid ambiguity, or it
has slightly different meaning, I'd like to know that, because I'm not sure
when to use which and why rustc complains about &_-ptr or such when I use
& instead of ref.


Reply to this email directly or view it on GitHub
#390 (comment)
.

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 12, 2015

Sorry, I don't understand your last answer. &x is dereferencing a pointer? I thought *x is dereferencing.

Perhaps I'm asking stupid questions, but the mental model I have is from C:

object  ----  reference operator &  ---> pointer
object  <--- dereference operator * ---- pointer

and there's no room in it for ref. If it's like this:

object  ---- ref operator ---> pointer

then in my mind it's identical with &, but clearly in Rust there must be a difference.

When I use & where Rust expects ref I get error message that I don't understand:

mismatched types: expected isize, found &_ (expected isize, found &-ptr)

I'm not sure what &_ and &-ptr are, but I'm guessing it means reference to an unknown type. And it's weird, because the type should be known from the context.

@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 12, 2015

So where are you seeing this error? In Rust, you basically have this:

let a = &i;
let r = *a;
// These are exactly like in C (i.e. `&i` gets the address of i,
// `*a` dereferences a)

And in pattern matching, you do this:

let a = Some(1i); // This is how we construct an Option
match a {
    Some(x) => println!("{}', x), // This prints '1'
    // Notice we "deconstruct" in a match statement the same way we
    // normally construct
    ...
}

What about when we have to get a reference in a match statement where there was
none before? In normal code, getting a reference to a variable is easy.

struct BigFoo {
    // Lots of fields
}

fn bar(bf: &BigFoo) {
    // Do stuff with bf
}

fn main() {
    let bf = BigFoo {
        // lots of fields
    }

    bar(&bf);
}

However, what if you have, say, a match statement?

fn possibly_print(x: &Option<BigFoo>) {
    match *x {
        // BAD: It's impossible to move out of an &-reference (the Option)
        //Some(bf) => println!("{:?}", &s),

        // GOOD: Instead, we take a reference into the `Option`s memory
        Some(ref bf) => println!("{:?}", *bf),
        None => println("No BigFoo!"),
    }
}

(Note: this is taken directly from the rust docs book)

Does that make sense?

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 12, 2015

The error I was getting was from

match a {
  Some(&x) => 
}

So I think I'm starting to get why it's illegal syntax:

fn main() {
    let x = 123;
    let &x_ref_1 = &x;
    let ref x_ref_2 = &x;
    //let & x_ref_3 = x; // same error as when matching &x
    let ref x_ref_4 = x;
}

It would be great if the tutorial explained exactly what happens in the cases above (they all print the same value, but I suppose Rust's automagic dereferencing hides the differences).

but "matching uses same syntax as constructing" doesn't seem right to me:

    let s1 = Some(&x); // legal
    let s2 = Some(ref x); // illegal
@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 12, 2015

There are two things going on here:

let x = 3i;
let s1 = Some(&x);
match s1 {
    Some(&y) => {}, // y = x here (it's destructured the same way it's constructed)
    ...
}

ref is only useful for match statements and the like; it's literally only for

let s2 = Some(x);
match s2 {
    Some(ref y) => {}, // y = &x here
    ...
}
@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 12, 2015

Oh, that's interesting! Are these two statements equivalent?

let x = &y;
let ref x = y;
@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 12, 2015

Yes, exactly!

On Mon, Jan 12, 2015 at 2:23 PM, Kornel notifications@github.com wrote:

Oh, that's interesting! Are these two statements equivalent?

let x = &y;let ref x = y;


Reply to this email directly or view it on GitHub
#390 (comment)
.

"Calling all. This is our last cry before our eternal silence."
"The lone and level sands stretch far and away"

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 12, 2015

Ok, so now it makes sense to me! Thanks!

I suggest adding such simple example to the site:

let y = 'y';

// `ref` on the left side of an assignment is like adding `&` on the right side
let ref x1 = y;
let x2 = &y;

println!("{}", x1 == x2);

The full example is useful as well, but with destructuring at the same time there's much more going on.

@ubsan

This comment has been minimized.

Copy link
Contributor

ubsan commented Jan 12, 2015

Thank you very much for bringing this incredibly confusing thing to our
attention; I hadn't even thought about it before you said it :)
On Jan 12, 2015 2:37 PM, "Kornel" notifications@github.com wrote:

Ok, so now it makes sense to me! Thanks!

I suggest adding such simple example to the site:

let y = 'y';
// ref on the left side of an assignment is like adding & on the right sidelet ref x1 = y;let x2 = &y;

println!("{}", x1 == x2);

The full example is useful as well, but with destructuring at the same
time there's much more going on.


Reply to this email directly or view it on GitHub
#390 (comment)
.

@mdinger

This comment has been minimized.

Copy link
Contributor

mdinger commented Jan 20, 2015

@pornel I just saw this thread and modified PR #421 to try to elaborate on this.

The specific file was pointers.rs

By the way, your critiques are very helpful. It's really hard to anticipate learning questions. Generally I focus on trying to fix things I find confusing but having others do the same is really useful.

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 20, 2015

@mdinger thanks. It's much clearer.

And I didn't know if let existed :)

@mdinger

This comment has been minimized.

Copy link
Contributor

mdinger commented Jan 26, 2015

ref and & are discussed here on reddit. let mut ref mut x = y I hadn't seen before. If my PR is generally accepted, I may try to work that in or refer to somewhere it can be discussed more thoroughly.

These explanations are missing in general and really need to be added.

@mdinger

This comment has been minimized.

Copy link
Contributor

mdinger commented Jan 30, 2015

Destructuring and pattern matching confusion discussed here.

I have a feeling pattern matching may need to be discussed as a separate topic from match. match uses pattern matching but it's also typically where it's explained.

@mdinger

This comment has been minimized.

Copy link
Contributor

mdinger commented Aug 9, 2015

Is this still a problem? Is there an actionable response that can be taken?

@kornelski

This comment has been minimized.

Copy link

kornelski commented Aug 9, 2015

Yes, I think it still can be improved.

The big example on that page focuses on a case where ref is necessary, but it doesn't make it clear how ref relates to &. There's too much going on for me to understand it.

For me it "clicked" when I saw the simplest case:

// `ref` on the left side of an assignment is like adding `&` on the right side
let ref x1 = y;
let x2 = &y;

So I suggest adding exactly that to the page, before showing a complex case.

@LukeSkyw

This comment has been minimized.

Copy link

LukeSkyw commented Feb 25, 2017

Just spent two hours trying to make sense of this:

struct Foo {
    value: u32,
}

fn double(v: &Vec<(Foo, u32)>) -> Vec<u32> {
    v.iter().map(|&(&f, v)| f.value + v).collect()
  |                ^^ expected struct `Foo`, found reference
  |
  = note: expected type `Foo`
  = note:    found type `&_`
}

fn main() {
    let vector = vec!(
        (Foo { value: 1 }, 2),
        (Foo { value: 2 }, 3),
        (Foo { value: 3 }, 4));
    let doubled = double(&vector);
    println!("{:?}", doubled);
}

So, as explained above, it should have been:

struct Foo {
    value: u32,
}

fn double(v: &Vec<(Foo, u32)>) -> Vec<u32> {
    v.iter().map(|&(ref f, v)| f.value + v).collect()
}

fn main() {
    let vector = vec!(
        (Foo { value: 1 }, 2),
        (Foo { value: 2 }, 3),
        (Foo { value: 3 }, 4));
    let doubled = double(&vector);
    println!("{:?}", doubled);
}
@AlecZadikian9001

This comment has been minimized.

Copy link

AlecZadikian9001 commented May 6, 2017

I find it strange that ref is only used on variables, but & is used on both types and variables. Adding in all the other type keywords creates other issues. * and & are both used for pointer types, raw and safe respectively, but with variables, * is used for dereferencing while & is used for enreferencing, for both raw and safe pointers. The "mut" keyword is inconsistently used too when you involve &, for example: mutable argument with fn foo(mut var: MyStruct) (mut comes before) and mutable pointer argument with fn foo(var: &mut MyStruct) (mut comes after).

@abbshr

This comment has been minimized.

Copy link

abbshr commented Aug 30, 2017

fn main() {
    if let Some(&pat) = Some(&Box::new(1)) {
        println!("{:?}", pat);
    }
}

The rust compiler explained as follow:

error[E0507]: cannot move out of borrowed content
 --> ref.rs:2:17
  |
2 |     if let Some(&pat) = Some(&Box::new(1)) {
  |                 ^---
  |                 ||
  |                 |hint: to prevent move, use `ref pat` or `ref mut pat`
  |                 cannot move out of borrowed content

It says "cannot move out of borrowed content, to prevent move, use ref pat or ref mut pat".

Besides, I think &Type is used to declare a reference type, &var is used to get the reference of the value (like a right-value), but with keyword ref, the ref var is used in pattern match to take out the matched value from a struct and bind its reference to var. Because &var can't be a left-value, so, using ref just in pattern match is undoubted~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment