Borrowing and traits

The following code compiles:

struct Struct {}
trait Trait {}

impl Trait for &Struct{}

fn read<T: Trait>(s: T) {}
fn write<T: Trait>(s: T) {}

fn main() {
  let s = Struct{};
  read(&s);
  write(&s);
}

But if I can't figure out how to call read(s) and write(s) from a function which itself takes an s: T:

struct Struct {}
trait Trait {}

impl Trait for &Struct{}

fn read<T: Trait>(s: T) {}
fn write<T: Trait>(s: T) {}

fn process<T: Trait>(s: T) {
  read(s);  //can't use s as this will move, but can't use &s either
  write(s);
}
fn main() {
  let s = Struct{};
  process(&s);
}

I'm guessing that by going through process, the compiler only knows that s: T, whereas before it knew that read and write were getting a &Struct. Is this right? And if so, what's the best way to fix it?

Thanks

You can use the following instead:

fn read<T: Trait>(s: &T) {}
fn write<T: Trait>(s: &T) {}

Yes, that's right.

The main question is, what's the intended behavior? For example, if you know in advance that Trait will only ever be implemented on shared references, you can add a Copy bound to it, since shared references are always Copy:

struct Struct {}
// this change
trait Trait: Copy {}

impl Trait for &Struct {}

fn read<T: Trait>(s: T) {}
fn write<T: Trait>(s: T) {}

fn process<T: Trait>(s: T) {
    read(s); // `s` is `Copy` due to trait bound, so it's copied and not moved
    write(s); // and it is available here
}
fn main() {
    let s = Struct {};
    process(&s);
}

Playground

Another way might be to use Clone instead of Copy and clone value to be used in read explicitly:

fn process<T: Trait + Clone>(s: T) {
    read(s.clone());
    write(s);
}

And the third possibility is that read does need the ownership, but does not need to really consume the value - it this case, you can modify its signature to return the thing it got:

fn read<T: Trait>(s: T) -> T { s }
fn write<T: Trait>(s: T) {}

fn process<T: Trait + Clone>(s: T) {
    let s = read(s);
    write(s);
}

Implement trait over reference is not a common pattern but sometimes useful. If it's not an intentional choice, it would be more idiomatic to change trait methods to take &self instead of self, and pass s: &T as suggested. If it's intentional but you know within the fn process the T is a reference actually, you can add T: Trait + Copy bound to tell the compiler the s can be used(copied) multiple times.

1 Like

Shouldn't you be implementing the trait transitively for references in this case?

struct Struct;
trait Trait {}

impl Trait for Struct {}
impl<T: Trait> Trait for &'_ T{}

fn read<T: Trait>(s: T) {}
fn write<T: Trait>(s: T) {}

fn process<T: Trait>(s: T) {
  read(&s);
  write(&s);
}

fn main() {
  process(&Struct);
}
1 Like

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.