Passing trait as a "superclass" reference

Hi,

I need a function accepting a reference to any object implementing only a certain trait.

Trait and implementations, spinner.rs:

pub trait Spinner {
    fn new() -> Self where Self: Sized;
    fn message(&self, line: String);
    fn stop(&mut self);
    fn adieu(&self, owner: &str) -> String {
        format!(
            "{}{}::spinner is already None.{}",
            BDELIM_ICON, owner, BDELIM_ICON
        )
    }
}

pub struct DaddySpinner {
    spinner: Option<daddy::SpinnerHandle>,
}

impl Spinner for DaddySpinner { ... }

pub struct CutieSpinner {
    spinner: Option<cute::SpinnerHandle>,
}

impl Spinner for CutieSpinner { ... }

For testing purposes I want something like this:

mod spinner;
use crate::spinner as spin;
use crate::spinner::Spinner;

fn cycle(spinner: &mut Spinner, _n: usize, _wink: usize) {
    spinner.message("Zzz".into());
    thread::sleep(Duration::from_millis(1000));
}

fn main() {
    let mut daddy = spin::DaddySpinner::new();
    cycle(&daddy, 0, 0);
    daddy.stop();

    thread::sleep(Duration::from_millis(1000));

    let mut cutie = spin::CutieSpinner::new();
    cycle(&cutie, 0, 0);
    cutie.stop();

    thread::sleep(Duration::from_millis(1000));

    println!("Hello, world!");
}

I can't figure out how to declare the spinner parameter of the function.

where Self: Sized

the compiler forced on me, then error:

error[E0308]: mismatched types
  --> src/main.rs:26:11
   |
26 |     cycle(&daddy, 0, 0);
   |           ^^^^^^ types differ in mutability
   |
   = note: expected mutable reference `&mut dyn spinner::Spinner`
                      found reference `&DaddySpinner`
...

Well, it wants a mutable reference and you're trying to give it an immutable one.

Also, you pretty much never want to have a new method in your trait.

I can't see how it is immutable. I hope my intent is clear; how would you do this, with the least rewriting possible?

&daddy is an immutable reference. &mut daddy would be an exclusive (mutable) one.

1 Like

Whether a reference is mutable depends on how you create it (&daddy vs &mut daddy) and not on whether you put a mut on the variable declaration. Putting mut on the variable declaration is necessary to allow you to create a mutable reference, but it doesn't make all references to it mutable.

1 Like

Yes!

This one is working:

fn cycle(spinner: &mut dyn Spinner, _n: usize, _wink: usize) {
    spinner.message("Zzz".into());
    thread::sleep(Duration::from_millis(1000));
}

fn main() {
    let now = Instant::now();

    let mut daddy = spin::DaddySpinner::new();
    cycle(&mut daddy, 0, 0);
    daddy.stop();

    thread::sleep(Duration::from_millis(1000));

    let mut cutie = spin::CutieSpinner::new();
    cycle(&mut cutie, 0, 0);
    cutie.stop();

    thread::sleep(Duration::from_millis(1000));

    println!("Hello, world!");
}

Can one do away with dyn and

pub trait Spinner {
    fn new() -> Self where Self: Sized;
    ...
}

Sized?

It's intentional that Rust is making you put dyn on the reference to the trait. The purpose of this is to make it clear whether it is an actual type, or whether you are using a trait as a type.


As for Self: Sized on the new method, no you can't do away with it. In general, as I mentioned above, it is generally recommended that you do not put constructor-like methods on traits.

Right here new() looks natural. To me, and so far, at least :slight_smile: . What would be the recommended approach to my case?

The recommended approach is to simply remove it. The constructor is normally an ordinary method on the struct rather than a trait method.

impl DaddySpinner {
    // non-trait methods go here
    fn new() -> Self {
        ...
    }
}

The only situation where it makes sense to have a constructor in a trait is where you need to create new instances in code that doesn't know what the actual underlying struct is. If you are writing DaddySpinner::new(), then you know what the struct is, so it does not apply to you.

1 Like

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.