I’m going through the interactive version of The Book, and I’m confused by the results of an exercise in Ch 4.3 - Fixing Ownership Errors.

The following code does not work, and they say it’s because it would result in the same heap space being deallocated twice:

fn main() {
    let s = String::from("Hello world");
    let s_ref = &s; // reference s
    let s2 = *s_ref; // dereference s_ref
    println!("{s2}");
}

But in my mind, this should be equivalent to the following compilable code, which transfers ownership of s to s2 :

fn main() {
    let s = String::from("Hello world");
    let s_ref = &s; // reference s
    let s2 = s; // move s directly
    println!("{s2}");
}

If s_ref is a reference to s, then dereferencing s_ref should return the String s, shouldn’t it? Why can’t s be moved to s2 with either the above code or let s2 = *&s;, which fails in the same way?

  • nous
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    1 year ago

    The example is trivial to optimize out, but what if that reference gets passed down a whole bunch of functions, potentially in a dynamic library the compiler has no visibility into at compile time? It can’t possibly track than and guarantee memory safety.

    It is possible it could track that. Might not be easy to do, and might slow down compile times. But even if it could IMO it shouldn’t. One of the things I really like about rust is all details about what a function can do are in the function signature. There are many things they could infer from the body of the function but do not (such as the return type). IMO this makes it far easier to read rust code as you don’t need to understand the body of a function to understand what can happen to the values you pass into it and get back from it.

    If this were allowed it would be trivial to break backwards compatibility of a library without any signature change - by having one version not take ownership of the value through the reference, then have a client rely on that behaviour, only to change the body to do a dereference and break the clients code.

    Currently in rust you cannot break downstream code without changing the function signature. Which is a very nice feature of rust to be able to keep backwards compatibility in libraries without as much effort on the developers side.

    And while it could do this if you don’t cross a function boundary, I still think it shouldn’t. For consistencies sakes. It adds far more cognitive load when you have features that work in some places but not others and becomes yet another rules you need to learn about what you can and cannot do.