Generalised tuple borrowing

Hey,

I am trying to solve the following problem:

  • Take some Args definition, such as a tuple (S, A).
  • Can I define a function that takes the original value or any combination of inner references, e.g. (&S, A), (S, &A) or (&S, &A), without multiple trait implementations.

Below I have appended a demo that gives you some idea of my thinking.

This, unfortunately, doesn't fit within the remit of Borrow or AsRef as the associated type needs to be parameterised by lifetimes. I suspect this is not something that can be implemented without GATs...

Does anyone have any suggestions?

Just a note on motivation:

  • The Function trait here has an associated type which must be the same for all reference combinations mentioned above.
  • With references themselves being treated as unique types (a good thing I would add), this leads to some rather hideous type constraints.
  • I would ideally have the constraint given as, say, Function<(S, A)>, and have this automatically support all of the aforementioned reference variants.

Many thanks,
Tom

pub trait Args<'a> {
    type Borrowed: 'a;
}

impl<'s, S: 's> Args<'s> for (S,) {
    type Borrowed = (&'s S,);
}

pub trait BorrowArgs<'a, A: Args<'a>> {
    fn borrow_args(&self) -> A::Borrowed;
}

impl<'s, S: 's> BorrowArgs<'s, (S,)> for (S,) {
    fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
}

impl<'s, S: 's> BorrowArgs<'s, (S,)> for (&S,) {
    fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
}

pub trait Function<A: for<'a> Args<'a>> {
    type Output;

    fn test<B: for<'a> BorrowArgs<'a, A>>(&self, _: B) -> Self::Output;
}

pub struct Demo;

impl Function<(f64,)> for Demo {
    type Output = bool;

    fn test<B: for<'a> BorrowArgs<'a, (f64,)>>(&self, _: B) -> bool { true }
}

fn main() {
    println!("{:?}", Demo.test((&1.0,)));
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:14:42
   |
14 |     fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
   |                                          ^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 |     fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:14:42
   |
14 |     fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
   |                                          ^^^^^^^
note: but, the lifetime must be valid for the lifetime `'s` as defined on the impl at 13:6...
  --> src/main.rs:13:6
   |
13 | impl<'s, S: 's> BorrowArgs<'s, (S,)> for (S,) {
   |      ^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:14:42
   |
14 |     fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
   |                                          ^^^^^^^

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/main.rs:18:42
   |
18 |     fn borrow_args(&self) -> (&'s S,) { (&self.0,) }
   |                                          ^^^^^^^
   |
note: ...the reference is valid for the lifetime `'s` as defined on the impl at 17:6...
  --> src/main.rs:17:6
   |
17 | impl<'s, S: 's> BorrowArgs<'s, (S,)> for (&S,) {
   |      ^^
note: ...but the borrowed content is only valid for the lifetime `'_` as defined on the impl at 17:43
  --> src/main.rs:17:43
   |
17 | impl<'s, S: 's> BorrowArgs<'s, (S,)> for (&S,) {
   |                                           ^

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0312, E0495.
For more information about an error, try `rustc --explain E0312`.
error: could not compile `playground`.

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

You have this definition:

impl<'s, S: 's> BorrowArgs<'s, (S,)> for (S,) {
    fn borrow_args<'a>(&'a self) -> (&'s S,) { (&self.0,) }
}

Note that I have allowed myself to add the elided lifetime 'a to make it more clear. So let's pick an example type, e.g. S = String. Let's also pick a lifetime 's = 'static. Since String contains no references, we have String: 'static, so the bounds on the impl are satisfied, and the impl should apply.

But what does borrow_args now do? It takes a reference to a (String,) of any lifetime 'a, and returns a &'static String. But 'static is a larger lifetime than 'a, so we can't reference anything inside self.

I think it should be more clear what's wrong now.

3 Likes

Hey, thanks for responding!

I agree that the definition I have provided is incorrect, and it is helpful to see the example you gave to clarify why that is precisely. Based on your comments I have found one working solution, in which BorrowArgs now has the following definition:

pub trait BorrowArgs<'a, A: Args<'a>> {
    fn borrow_args(&'a self) -> A::Borrowed;
}

The addition of the lifetime 'a on self allows you to correctly require that the lifetimes are compatible. It's not ideal still, and GATs may make this cleaner in the future, but it will do for now.

Thanks a lot for your help,
Tom

1 Like

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