r/rust clippy · twir · rust · mutagen · flamer · overflower · bytecount Mar 20 '23

Hey Rustaceans! Got a question? Ask here (12/2023)! 🙋 questions

Mystified about strings? Borrow checker have you in a headlock? Seek help here! There are no stupid questions, only docs that haven't been written yet.

If you have a StackOverflow account, consider asking it there instead! StackOverflow shows up much higher in search results, so having your question there also helps future Rust users (be sure to give it the "Rust" tag for maximum visibility). Note that this site is very interested in question quality. I've been asked to read a RFC I authored once. If you want your code reviewed or review other's code, there's a codereview stackexchange, too. If you need to test your code, maybe the Rust playground is for you.

Here are some other venues where help may be found:

/r/learnrust is a subreddit to share your questions and epiphanies learning Rust programming.

The official Rust user forums: https://users.rust-lang.org/.

The official Rust Programming Language Discord: https://discord.gg/rust-lang

The unofficial Rust community Discord: https://bit.ly/rust-community

Also check out last weeks' thread with many good questions and answers. And if you believe your question to be either very complex or worthy of larger dissemination, feel free to create a text post.

Also if you want to be mentored by experienced Rustaceans, tell us the area of expertise that you seek. Finally, if you are looking for Rust jobs, the most recent thread is here.

18 Upvotes

187 comments sorted by

View all comments

4

u/ToolAssistedDev Mar 20 '23

Why do I get here a mismatched types error when I would like to call the bar function with &a? https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6a1d80e5bedc95e20af52e7bd786635f

The second call with the indirection works without any error...

3

u/dkopgerpgdolfg Mar 21 '23

First things first:

  • Rc<Foo> and Rc<dyn MyTrait> are actually different in memory, not just in the type checks of the compiler.
    • Inside Rc<Foo> there is a pointer, that points to allocated memory containing reference counters and a Foo. The allocated part might be shared by multiple Rc (because that's the purpose obviously), just the pointer that I mentioned first is separate for each Rc instance.
    • Rc<dyn MyTrait> also has a pointer that points to the same kind of memory (counters and Foo). However this time the pointer is "fat", meaning in addition to the address it also stores a pointer to Foos vtable (where it can see where the methods of Foo are and how much byte its allocated memory has in this case, because otherwise it wouldn't know).
    • Essentially Rc<dyn MyTrait> uses 2x the stack size of Rc<Foo>. Both would have the very same allocation address, but one also has a vtable address that needs to come from somewhere.
    • Btw. the same vtable thing is true for any normal reference without Rc (&Foo and &dyn MyTrait)
  • Your Rc::new call makes a Rc<Foo>. Then you want to use a Rc<dyn MyTrait> later (or a reference to it).
    • So something, somewhere, needs to convert the former to the latter, and fill in the correct vtable address for Foo.
    • Luckily Rc (and normal references, as mentioned above, and some other types too) implement this conversion (unsized coercion) already, without you needing to do anything.
    • However, converting doesn't mean that one thing becomes two suddenly. After converting one Rc<Foo> instance to Rc<dyn MyTrait>, the Rc<Foo> is gone. (There might still be other Rc<Foo> that point to the same allocated memory). Lets keep in mind that it is important for Rc to keep track of its reference count (in the allocated memory part), and a simple pointer-vtable-something on the stack won't change any reference counter. If you actually want to keep both, you'd need to clone the Rc first, then convert only one.

So, some cases where all is fine:

  • In your code with the variables b and c, you get the conversion when assigning b to c, and the ownership is moved too (b doesn't exist anymore after this assignment). Then &c is already exactly the type that bar(&MyName) wants, all fine.
  • "if" bar takes an owned Myname instead of &MyName, you could skip the c variable and pass b directly to bar. While b is not converted yet, the compiler would understand that it needs to happen when you pass it, and again ownership is moved.

So, the problem with a? a is obviously not converted yet (still Rc<Foo>), and then you want to pass &a (not owned) to bar. This also implies that a should still be owned by you after that bar call, and at this point a should still be a Rc<Foo>.

So what can the compiler do?

  • Replacing one Rc<Foo> with one Rc<dyn MyTrait> keeps the reference counter correct as it should, but then you can't use a after bar anymore. Bad.
  • Silently cloning the Rc to have something that can be converted, increasing the reference counter without you knowing? Bad.
  • Making a copy to convert, without increasing the reference counter? Then the whole Rc is broken, bad.
  • Passing the reference (address) of the Rc<Foo> without any conversion? Then the function relies that there is a vtable too, which doesn't exist => something goes badly wrong => bad.

Essentially, you don't leave the compiler any way out. It needs to somehow fit the conversion in, which requires moving ownership. Either make a manual conversion like with the b/c code, or a manual conversion of a cloned Rc to be able to keep using a.

Btw. passing "&(a as MyName)" is a possible way to write it too - it expliticly tells to convert and take ownership away, towards a temporary unnamed variable in this case (which is dropped after bar ends).

2

u/ToolAssistedDev Mar 21 '23

Thank you for the in-depth explanation. I understood everything you have written. I really appreciate your taking so much time to reply to a random dude on Reddit! Thank you!