Confused about moves, borrowing etc. Concrete example

Hi all, I'm new to Rust and I'm working my way through the documentation. My background is in high-level OOP languages like Java, C#, VB, so there is a bit of a paradigm shift here for me. It's mostly straightforward, but no matter how many times I think I understand borrowing, it turns out I'm wrong.

I have the following piece of code, which the compiler hates. Specifically it tells me in the summarize_author() implementation under news article "cannot move out of 'self.author' which is behind a shared reference".

This doesn't completely make sense to me, so I was hoping someone could explain it. If I return self.author.clone() it works, but I'm not sure I'm clear on why. Is the default behaviour to transfer ownership of a String if you return it from a method? If so, what's the correct way to do this? Create a copy with .clone(), or is there a more Rusty approach?

pub trait Summary {

    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {

        format!("Read more from {}...", self.summarize_author())

    }

}

pub struct NewsArticle {

    pub headline: String,

    pub location: String,

    pub author: String,

    pub content: String,

}

impl Summary for NewsArticle {

    fn summarize_author(&self) -> String {

        self.author

    }

    fn summarize(&self) -> String {

        format!("{}, by {} ({})", self.headline, self.summarize_author(), self.location)

    }

}

Yes, the default behavior for most types is that they must be cloned explicitly if you need to have an independent copy. The exception is so called Copy types that can be copied implicitly because that is cheap and doesn't have any side effects. Examples of Copy types are primivite integers (e.g. u32) and shared references (e.g. &str).

The signature of your method states that it returns String, so you need to provide a value of this type. String is not a Copy type, so you cannot implicitly copy it like a u32. You need to create a new String object or take it from somewhere else. If you want to avoid a full copy of the string, you need to use a different return type.

You can return a &str if you want to borrow the string contained in self. If some implementations of the trait cannot return a borrowed value (e.g. if the string returned by summarize_author is not stored inside self but generated dynamically), you can use Cow<str>. Another option is to use Arc<str> which will allow you to keep multiple owning pointers to the same string buffer, although that will require you to store the string as Arc<str> inside the NewsArticle as well.

2 Likes

Thank you that's quite illuminating. So if I'm understanding this right: I can't hand off self.author because I'm borrowing it from self (which isn't what I want to do anyway). And a String won't be copied by default because it's not a primitive. I could return a reference to the String instead if I change my return type, or I can copy the value.

Please tell me this becomes second nature at some point! :slight_smile:

Primitive doesn't matter. Those primitive number types(like i32, u8, f64 etc) implement the Copy trait, which marks that single bitwise copy is enough to make an owned copy of this type. String owns its buffer holding the text content. If we bitwise copy this struct(String) we get 2 owned Strings pointing same buffer, which results double free when both are dropped.

To copy the String you need to allocate another buffer and copy its content, which requires some code not just single bitwise copy. Rust doesn't run arbitrary code on assignment/return. To run those code you need to call .clone() method explicitely.

1 Like

It's not just primitives, almost all types that are relatively small and don't allocate on the heap are Copy. When you define your own types, you can add #[derive(Copy, Clone)] on the line above to implement that behaviour as well.

The idea behind it is that any potentially expensive operation should never be done implicitly. Strings can be big, so implicitly copying them could have huge performance implications. Primitives and small structs can't be big, so they are allowed to be implicitly copied. It's a different mindset than a managed language, but it definitely does get a lot easier once you get used to it :slight_smile:

1 Like

Thanks all. That makes a lot of sense and I think is the context I needed to make sense of this. Much appreciated.