A lot of questions about dereferencing and coercion

Sorry for the long post, but I have a lot of questions about dereferencing and coercion. I’ve spent my last days trying to study them and make a lot of examples and every time that I feel that I got them I always find a situation that get me lost!

I was not able to find in the Rust book a place where is cleared explained why the following code cannot compile:

struct X(i32);

fn main() {
    let x: &X = &X(0);
    let y = *x; // <-- Error here
}

I was expecting an error reported only if I tried to use x after I moved its content in y.

1 bis)
Similar question for Rc<T>

use std::rc::Rc;

#[derive(Debug)]
struct X(i32);

fn main() {
    let x = Rc::new(X(0));
    let y = *x; // <-- Error here
}

In this case I guess that is due to avoid that someone move the content bypassing the reference counter. So this is more understandable to me.

Deref documentation states that deref method (that is the operator * as I understood) returns a value of type &Deref::Target but if you check this little example it seems to return Deref::Target. So actually operator * does something more than just calling deref method?

use std::ops::Deref;

struct X(i32);

impl Deref for X {
    type Target = i32;
    
    fn deref(&self) -> &i32 {
        &self.0
    }
}

fn main() {
    let x = X(0);
    let y: i32 = *x;
}

In the STD library it seems to me that Deref trait is more used because of the deref coercions rule than the use of operator *.
For example Vec<T> give you an error if you try to use the operator directly on its instances, but it's used a lot to pass vector as slice (because of coercion). I found other cases, but now I am not able to remind them.

Deref coercions sometimes get me confuse. For example consider this little test:

use std::rc::Rc;
use std::cell::RefCell;
use std::cell::Ref;

#[derive(Debug)]
struct X(i32);


fn main() {
    let x = Rc::new(RefCell::new(X(0)));
    let y: Ref<X> = x.borrow(); // <-- This take &self that is coerced to RefCell
                                //     I was expecting to call Rc::borrow (before checking its documentation)
                                //     What if I want to call Rc::borrow?
}

How can I invoke the borrow method on the Rc instance and avoid the coercion?

Ok... I think it's enough for today! :wink:

  1. Your two testcases are essentially the same from the perspective of the compiler: you're not allowed to move out of a reference. (Rc provides a method try_unwrap() which essentially moves out the content of an Rc.)

  2. Yes, it has the behavior you found: it calls deref, then dereferences the returned reference.

  3. Yes, a lot of dereferences are implicit; occasionally there are cases where the compiler can't figure it out automatically, and you'll see constructs like &**val.

  4. The Borrow trait isn't in scope by default; you have to use it explicitly with "use std::borrow::Borrow;".

2 Likes

For 1, consider this example which was borrowed from a rustbyexample PR (pasted below):

struct Empty;

fn bad_consumer<'a>(x: &'a Empty) {
    {
        // Suppose this `move` worked. Then immediately after transferring
        // the data, the new variable would go out scope and be erased.
        // `bad_consumer` would outlive `x` and `empty` from `main()`
        // would refer to erased data. These two things go hand in hand
        // and so both are banned.
        let _eraser = *x;
    }
}

fn main() {
    let empty = Empty;

    bad_consumer(&empty);
}

Hence, this is prevented because the input x has type &Empty which is a borrow. If you were allowed to yield ownership to something else from a borrow, you would be breaking what it means to borrow it (it must be returned). Your question is exactly the same because you used the & operator. You just don't happen to have a function. If you just want it to work, do this:

#[derive(Clone, Copy)]
struct X(i32);

fn main() {
    let x: &X = &X(0);
    // Dereference and then save a copy in the new variable`y`.
    let y = *x;
2 Likes

Regarding the Deref question (2): Returning Deref::Target by value would in fact consume the value; we could not ever dereference without doing so. Also returning &Deref::Target allows some cool tricks like dereferencing to something that's not really contained (I'm doing this for the OptionBool type in my optional crate for some nice speedups when using slice derefs).

Regarding question (2) I saw that other operators seem to automatically deference the returned value.
For example consider this little test made with the Index trait:

use std::ops::Index;

struct X(i32);

impl Index<i32> for X {
    type Output = i32;

    fn index<'a>(&'a self, index: i32) -> &'a i32 {
         &self.0
    }
}

fn main() {
    let x = X(1);
    let y: &i32 = x[6]; // <-- Error: Returned type is i32
}

Which is the reason of this behaviour? Where is specified that some operators (which ones?) automatically deference the returned value, and how is it possible (see your answers to my question (1))?

Please note that the tone of this question is not polemic at all, I'm just curious to understand better basic aspect of this interesting language.

Again, the index function returns a reference. It is the [..]-operator that inserts the dereference if applicable. Otherwise vectors of numbers would return references to their contents on indexing, which would be quite surprising, don't you think?

You can use &x[6]in your code to counteract the dereferencing.