Abstract over Associated type mutability

I have a trait that defines an associated type used in its methods signature, for example:

  trait MyTrait {
      type REF;
      fn do_something(value: &Self::REF) -> ();
  }

Then I have two structs that implement this trait; the first struct needs a simple reference to 'value', on the contrary, the other one requires a mutable reference and I am not able to implement it.
For example, my two structs could be:

  struct SimpleRef{}
  impl MyTrait for SimpleRef {
      type REF = String;
      fn do_something(value: &Self::REF) -> () {
          //use value
      }
  }
  
  struct MutateRef{}
  impl MyTrait for MutateRef {
      type REF = String;  // HERE I NEED TO DECLARE THAT THE TYPE REF IS MUTABLE.
                          // I tried with AsRef, AsMut, BorrowMut without luck
      fn do_something(value: &Self::REF) -> () {
          // MUTATE value
      }
  }

Is there a way to define the type REF in MutateRef to get the desired behaviour? So I can call the SimpleRef methods with a '&' and when I use MutateRef I have to pass '& mut' instead?

Can you change the trait definition? Because then you can change it to be something like this:

trait MyTrait {
    type REF;
    fn do_something(value: &mut Self::REF);
}

or something like this:

trait MyTrait {
    type Data;
    type Ref: Deref<Target=Data>;
    fn do_something(value: Self::Ref);
}

and then you can impl the second one like so:

impl MyTrait for MyStruct {
    type Data = [u8];
    type Ref = &mut [u8];
    fn do_something(value: &mut [u8]) {}
}

or like so:

impl MyTrait for MyStruct {
    type Data = [u8];
    type Ref = &[u8];
    fn so_something(value: &[u8]) {}
}

Otherwise you can use something that has interior mutability, like RefCell or Mutex

2 Likes

Hi @OptimisticPeach

thank you very much!
Your second solution is exactly what I was looking for; anyway, to be able to compile your code I was forced to add an explicit lifetime to the structs:

trait MyTrait {
    type Data;
    type Ref: Deref<Target=Data>;
    fn do_something(value: Self::Ref);
}

struct MyStruct<'a> {}

impl <'a> MyTrait for MyStruct<'a> {
    type Data = [u8];
    type Ref = &'a Self::Date;
    fn do_something(value: Self::Ref) {}
}

struct MyStructMutable<'a> {}

impl <'a> MyTrait for MyStructMutable<'a>  {
    type Data = [u8];
    type Ref = &'a mut Self::Date;
    fn do_something(value: Self::Ref) {}
}

Am I doing it correctly?
I love your solution but I am still not able to understand why and how it works :sweat_smile:

Oh right, I forgot about the lifetime attached the reference. In any case, you want to attach the lifetime to the trait, not the struct. Your example would transform into:

use std::ops::Deref;

trait MyTrait<'a> {
    type Data: ?Sized;
    type Ref: Deref<Target=Self::Data> + 'a;
    fn do_something(value: Self::Ref);
}

struct MyStruct;

impl<'a> MyTrait<'a> for MyStruct {
    type Data = [u8];
    type Ref = &'a Self::Data;
    fn do_something(value: Self::Ref) {}
}

struct MyStructMutable;

impl<'a> MyTrait<'a> for MyStructMutable  {
    type Data = [u8];
    type Ref = &'a mut Self::Data;
    fn do_something(value: Self::Ref) {}
}

A few major changes:

  • type Data is now ?Sized because [u8] is not Sized
  • MyTrait now has a lifetime attached
  • MyStruct and MyStructMutable now don't have a trait attached because it would have nowhere to be used in the struct, considering they're unit structs.
5 Likes

@OptimisticPeach

Thanks again. That's brilliant!

1 Like

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