Another head-scratcher


#1

With much help from some of you, I have an approach to the global-state and cached-sqlite-statement issues that were plaguing me and I am proceeding to port my personal finance manager from C to Rust. However, I’ve run into a compiler error that I don’t understand and would appreciate some help. I’ve come up with a small example that illustrates the problem, which I will get to after showing you a few even simpler examples that work as expected.

Let’s start with this:

struct Test {
    foo:String
}

fn main() {
    let bar = Test {
        foo:"This is a test".into()
    };
    println!("{}", bar.foo);
}

This compiles and runs as you would expect. As does this:

struct Test {
    foo:Option<String>
}

fn main() {
    let bar = Test {
        foo:Some("This is a test".into())
    };
    println!("{}", bar.foo.unwrap());
}

This also works:

use std::rc::Rc;

struct Test {
    foo:String
}

fn main() {
    let bar = Rc::new(Test {
        foo:"This is a test".into()
    });
    println!("{}", bar.foo);
}

But this:

use std::rc::Rc;

struct Test {
    foo:Option<String>
}

fn main() {
    let bar = Rc::new(Test {
        foo:Some("This is a test".into())
    });
    println!("{}", bar.foo.unwrap());
}

results in:

dca@franz:/tmp/testit$ cargo run
Compiling testit v0.1.0 (file:///tmp/testit)
error[E0507]: cannot move out of borrowed content
--> src/main.rs:11:24
|
11 |         println!("{}", bar.foo.unwrap());
|                        ^^^ cannot move out of borrowed content

error: aborting due to previous error

(I’m having some difficulty with formatting here, despite using “preformatted text”. The rendering of the code in the preview appears to be Courier or some such fixed-width font, the '^^^" is not landing in the right place. It should be directly under “bar” in the line above.)

All of these little examples were run on a Debian Linux system with the 1.25 compiler installed.

Can someone explain this? Thanks.


#2

You need to add as_ref:

println!("{}", bar.foo.as_ref().unwrap());

unwrap() takes self- an Rc gives you shared access to the underlying value, but attempting to call unwrap on it will try to move the value out of the option, which it can’t cause it’s borrowed.

as_ref() turns an Option<T> into an Option<&T> - unwrap’ing that takes a copy of the immutable reference that’s in there, which is fine as it leaves the value intact.


#3

Just to expand on this slightly, bar is being “moved” by your code because it isn’t being borrowed (which means to take by reference). as_ref() changes that default


#4

I don’t think that’s correct. If it were, then

use std::rc::Rc;

struct Test {
        foo:Option<String>
}

fn main() {
        let bar = Rc::new(Test {
                    foo:Some("This is a test".into())
                        });
            println!("{}", (&bar).foo.unwrap());
}

would work, and it doesn’t. It produces the same error as the original. What Vitaly is saying is that Rc gives you a shared reference, a non-mutable borrow, in this case to something with type Option<String>. When you try to extract the contents of the Option with unwrap, because the contents is owned, you are attempting to move something from a container you don’t own. So as_ref is needed to turn the thing being unwrapped into an Option<&String>, so now the contents of the Option is borrowed, not owned. What he suggested works.

Vitaly – thanks for a nice, clear explanation, as usual. See my post in the Rust 2018 conversation. Maybe you are the guy to write what I think is needed.


#5

When I said as_ref “turns” it into Option<&T>, I meant in a conceptual sense. The technical aspect is it returns a new Option, which is borrowing the T from the original Option. So the field itself isn’t being turned into anything in-place, so to speak. Just wanted to make sure this bit is clear.

I could certainly try but I doubt I’d have enough free time to do that. Instead, I try to answer bits here and there on this forum instead :slight_smile:


#6

I wasn’t really thinking much one way or the other about how as_ref does what it does. The key point that you got across to me was that the unwrap was trying to move something out of a borrowed container.

As for how to spend your time, perhaps what you are already doing is more valuable than trying to write yet another book on Rust, because you are assisting people with specific problems and you have a gift for doing this with clarity. Not everyone who thoroughly understands the subject material can teach it, as I’m sure you’ve experienced in school, as have I. Anyone who has read “The Character of Physical Law” or the Red Books has experienced the work of someone, Richard Feynman, who had both abilities on the rarest of levels.


#7

Right - that’s what I was trying to convey. But then we got into some details here and I just wanted to clarify lest someone else reading this misinterprets.

I appreciate the compliment, particularly from a “tough customer” like yourself Don! :slight_smile: Trying to answer questions is also a way for me to ensure I myself understand (or don’t!) the stuff, so it helps me as well in a way.