Mutable reference to self between different traits


#1

I am trying to create a (public) trait which contains a single visible function. The default implementation of this function is rather complex, and calls a number of helper functions which I implemented as a separate, private, trait. I am having trouble passing a mutable reference to self, however. Consider the following (simplified) example:

trait X
{
    fn x(&mut self)
    {
        println!("in x()");
    }
}

impl<T> X for T {}

trait Y
{
    fn y(&mut self)
    {
        self.x();
    }
}

impl<T> Y for T {}

fn main()
{
    let mut i = 32;
    i.y();
}

This does not compile, with the compiler giving the error

error[E0596]: cannot borrow immutable argument `self` as mutable
  --> trait.rs:15:9
   |
15 |         self.x();
   |         ^^^^
   |         |
   |         cannot reborrow mutably
   |         try removing `&mut` here

The error message isn’t particularly helpful (there is no “&mut” at the specified position), so I don’t know what I’m doing wrong here. Any help would be greatly appreciated.


#2

You can make this code compile by making it explicit that trait Y depends on trait X.

trait Y: X

Now, as for why the original code does not compile, I am not sure… might be a bug in the compiler, or in my understanding of reborrowing and blanket trait impls. Take your pick :slight_smile:


#3

Another option is a where clause.

fn y(&mut self) where Self:Sized

I am not sure what is the detail beyond this.


#4

To get it to compile add where Self: Sized to either trait Y or fn y(&mut self)

I think the confusing error is maybe down do the compiler taking the implementation of trait X to be &mut i32 since i32 isn’t allowed without Sized.


#5

The error message is definitely subpar - it warrants a bug report IMO.

The issue is Self can be just about anything in Y::y() but X is only implemented for T: Sized. What you likely intended is impl<T: ?Sized> X for T {}


#6

Thanks for your reply. It does solve the problem for the test program, but would leak the private trait in my code. Since that was the whole point of this excersise, that option is out for now :slight_smile:

Huh, thanks, that works. Could you perhaps explain why, or perhaps point me to some relevant documentation?


#7

Welcome to a good example of a programming language design corner case :slight_smile:

Rust has limited support for so-called dynamically sized types (DSTs), whose size is unknown at compile time. Examples of dynamically sized types include slices ([T]) and trait objects (simply called Trait today, will be called dyn Trait in the future).

Dynamically sized types have many limitations, some of which are fundamental and some of which may be lifted in future editions of Rust. Here are some examples:

  • A struct may only have one dynamically sized member
  • You can only use them indirectly through a reference
  • You cannot pass them as parameters to a function
  • You cannot return them as function results

In the Rust type system, the Sized trait is used to indicate that a type has a size known at compile-time. All “normal” types implement the Sized trait automatically. Dynamically sized types, however, do not.


Now, this is where it gets confusing but please bear with me: Rust generics implicitly require their input type to be Sized. Whenever you write…

fn generic<T>(input: T) { /* ... */ }

…what the Rust compiler implicitly understands is this:

fn generic<T: Sized>(input: T) { /* ... */ }

If you want your generic function to accept dynamic sized types, you must explicitly ask for it using the ?Sized syntax, like this:

// NOTE: Added a Box because you can't pass DSTs to functions
fn generic<T: ?Sized>(input: Box<T>) { /* ... */ }

The same, however, does not hold for traits. By default, traits are not assumed to require a Sized bound on the implementor. Rust will be fine with this…

trait MyTrait {}

impl<T> MyTrait for [T] {}

…as if you wrote the following:

trait MyTrait: ?Sized {}

impl<T> MyTrait for [T] {}

If you want any type implementing your trait to be Sized, you must ask for it:

trait MyTrait: Sized {}

// Implementing MyTrait for slices is now illegal

These rules may seem needlessly inconsistent at first sight. And certainly, they are surprising the first time you encounter them. But they were chosen in order to minimize the odds that people need to know or think about dynamic-sized types in day-to-day usage of Rust:

  • DSTs are rare and impose a lot of restrictions on generic implementations, so we usually want the argument of a generic function/impl to be Sized.
  • On the other hand, people who declare traits usually want them to apply to as many types as possible, including DSTs. Defaulting traits to ?Sized allows for this.

And with these concepts in place, we can now explain what is going on!


In your current code, you are defining a trait X which can be implemented for any type, whether dynamically sized or not…

trait X  // rustc reads: "trait X: ?Sized"

…and then you are implementing it for all Sized types (not dynamically-sized ones):

impl<T> X for T {}  // rustc reads "impl<T: Sized> X for T {}"

After that, you define a trait Y which can again be implemented for any type (Sized or not)…

trait Y  // rustc reads "trait Y: ?Sized"

…but then you rely on functionality from X, which as we explained earlier is only implemented if the implementor of the trait (Self) is sized:

trait Y
{
    fn y(&mut self)
    {
        // Requires functionality from X, which is only implemented
        // if the implementor of Y, Self, is Sized.
        self.x();
    }
}

Here is why the various proposed solutions work:

  • Y: X means that Y may only be implemented for types which implement X. Therefore, the problem is trivially solved: Y can freely rely on functionality from X.
  • fn y(&mut self) where Self:Sized means that the function Y::y() is only provided if the implementor of Y (called Self) is Sized. Since X is implemented for all Sized types, the problem is solved.
  • impl<T: ?Sized> X for T {} forces X to be implemented for dynamically sized types, therefore X is now implemented for all types. So again, the problem is solved.

I agree with @vitalyd that the heart of the issue here is that the compiler error message is very misleading, largely unrelated to the issue at hand, and should probably be improved. A bug report seems warranted.

And if you want more material on Dynamically Sized Types, the language reference and the Nomicon have your back.


Broken error message? "(expected &Foo, found &Foo)"
#8

Also worth noting that the error message is much better (and to the point) if you use UFCS:

// Everything else as in the original code
trait Y {
    fn y(&mut self) {
        X::x(self)
    }
}
error[E0277]: the trait bound `Self: std::marker::Sized` is not satisfied
  --> src/main.rs:11:9
   |
11 |         X::x(self)
   |         ^^^^ `Self` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `Self`
   = help: consider adding a `where Self: std::marker::Sized` bound
   = note: required because of the requirements on the impl of `X` for `Self`
   = note: required by `X::x`

#9

This is an absolutely stellar reply. Thanks for writing it up.


#10

Good tip, I’ll keep it in mind for when I find myself in a similar situation again.

Many thanks to all who have replied and have proposed solutions, and especially to @HadrienG for his extensive explanation of why the error occurred.

I’ll look into creating a bug report for the error given by the compiler, since the current error message is rather confusing.


#11

Well, I’d extend the thanks to @vitalyd, @nakacristo, and @jonh, whose other solutions to this problem allowed me to figure out what is going on :slight_smile: