Can build errors show what is trying to get a mutable reference?

I'm having these conversations with the borrow checker, as one does, and it goes E0596! and then patiently explains that it simply can't borrow this as mutable and points to the exact spot in my source file where it ran in to the problem:

self.inner.by_ref().map(…)
^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable

But that's not quite enough to know where we're not seeing eye to eye. The linked explanation for E0596 says

This error occurs because you tried to mutably borrow a non-mutable variable.

but I didn't know I tried to mutably borrow a non-mutable variable. There is not a single & or mut or borrow in that source listing it's showing.

So I guess it must be from the .by_ref()? What's the signature? Where is it declared? Is it something that's implemented on the type of self.inner, or is it from some other trait it's picked up? Which one? Does it have a link to the rustdoc for it? That would be helpful.

Cargo build ends with

To learn more, run the command again with --verbose.

but that does not seem to have added anything that looks like it answers any of those questions.

Is there a different build flag I should be using to get more visibility in to what's going on?

Or does this mean it's time for me to get set up with an IDE with proper rust type inspection and code navigation? I have been thinking about doing that, but since I'm only just barely growing past my main function, I haven't yet looked too closely at what's available there.

Thanks rustaceans! I think we're going to get along well once I figure out who keeps moving my cheese.

Do you have some code you can link to, ideally as a repro in https://play.rust-lang.org?

This is likely fron Iterator::by_ref, but without more context I can't say why.

Then we are in the same boat! And thus the question: How do we get the type system to give us more context about what it's thinking, so we can see what we've overlooked?

I'll go ahead and make something in the Playground, so I can see how the Playground works if nothing else. But since I don't think such a tool would be so special-purpose as to only analyze by_ref problems, or only E0596 errors, I am not sure how much my example will help.

You can ususally get types by doing

let () = expr;

This will raise a type error which will helpfully have the type of expr. (and if you don't get a type error, then expr has type ())

Hmm. Change the code and rebuild to produce more errors, in hopes that the new errors shed light on the other errors?

Reminds me of the particle collider experiments physicists do where they see what they can learn by smashing things together really hard and trying to catch a glimpse of what pieces fall out when they break.

Well, here's a playground link:

and if I add that extra error as you suggest, right before the line with the borrowing error, as let () = self.inner.by_ref; it then tells me

attempted to take value of method by_ref on type std::env::ArgsOs

Hmm. I guess if I didn't know inner was an ArgsOs, that's one way to find out. But it doesn't tell me anything about why by_ref would be trying to get a mutable reference.

I can take that and try searching ArgsOs::by_ref on doc.rust-lang, but that turns up empty. fn:by_ref has a bunch of results, but none of them say ArgsOs. I could make guesses, since I happen to know we're doing some iterating, but I bet lots of kinds of things do some iterating.

It would be a shame to rely on my guesses! The compiler knows way more about this than I do!

When looking at the docs of ArgsOs, the very first line is

An iterator over the arguments of a process, yielding an OsString value for each argument.

Hmm, ok, ArgsOs doesn't have any methods on it directly, but the docs say that it is an Iterator. Scrolling down, yes there is an Iterator implementation. Let's look that.

In Iterator there is a method by_ref. That's it!

Iterator::by_ref is defined as such,

fn by_ref(&mut self) -> &mut Self

Well, there is where your unique (mutable) reference comes from. Your shared (immutable) reference comes from self. Your method is likely defined with &self. Just change that to &mut self. No guesswork, just analyzing the type and looking through the docs. (Although it does take a bit of getting used to the Rust docs)

Now, looking at your playground: I can confirm all this in fn iter_bytes(&self). There is the shared reference, and self.inner.by_ref() is the unique reference (from Iterator::by_ref).

With Rust I would keep a few traits in the forefront because of how fundamental they are: Clone, Iterator, Debug, Display, and From/Into. These traits are pervasive and it really helps to know what they provide. Iterator especially, Iterator is probably the single best abstraction in Rust.

That's a fun analogy

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.