Designing a generic trait representing either owned or borrowed data

#1

I’m currently faced with implementing a generic abstraction over textual data.

I need to express a type that can hold both references and owned data, like String, &str, Cow<'_, str>, Arc<T>, …

You can see my attempt here: https://github.com/graphql-rust/graphql-parser/pull/19

(simplified)


trait Text<'a> : From<'a str> + AsRef<str> + Clone ... {}

impl<'a> Text<'a> for &'a str;
impl Text<'static> for String;
...

Now, ideally I’d like to be able to do:

struct<'a, T: Text<'a>> AstNode {
  name: T
}

Sadly, this doesn’t work, because T is not constrained by 'a.

The best I’ve been able to come up with is this:

struct Str<'a, T: Text<'a>> {
  value: T,
  __marker: std::marker::PhantomData<&'a T>,
}

struct AstNode<'a, T> {
  name: Str<'a, T>,
}

This actually works fine and should be zero-cost at runtime, but the need for the wrapper type is a bit unfortunate.

Maybe there is some other approach someone could come up with?

#2

You could say for<'a> From<&'a str> as the trait bound to get rid of the lifetime on Text

If that doesn’t work, then defer the Text bound to when you make Str instead of putting it on the type itself.

#3

Uh, I just though of a different solution that does not require a wrapper type.

trait Text<'a> {
    type Out: 'a + AsRef<str> + From<&'a str> + PartialEq + Eq;
}

impl<'a> Text<'a> for &'a str {
    type Out = Self;
}
impl Text<'static> for String {
    type Out = Self;
}

struct Node<'a, T: Text<'a>> {
    value: T::Out,
}

fn compare<'a, T: Text<'a>>(a: T::Out, b: T::Out) -> bool {
    a == b
}

fn main() {
    let a = Node::<&str>{ value: "blub" };
    let b = Node::<&str>{ value: "blub" };
    dbg!(a.value == b.value);
}
#4

By the way

impl<'a> Text<'a> for String { type Out = Self; }

Should work

#5

and is more general (assuming traits are invariant w.r.t lifetime parameters)

#6

I tried that, but that gives me:

trait Text {
    type Out: AsRef<str> + for <'a> From<&'a str> + PartialEq + Eq;
}

impl<'a> Text for &'a str {
    type Out = Self;
}
error[E0277]: the trait bound `for<'a> &str: std::convert::From<&'a str>` is not satisfied
 --> src/main.rs:5:10
  |
5 | impl<'a> Text for &'a str {
  |          ^^^^ the trait `for<'a> std::convert::From<&'a str>` is not implemented for `&str`
#7

: for<'a> From<&'a str> implies : From<&'static str> which rules out &'_ str.
You need indeed to carry an explicit lifetime parameter around, since that’s what you would have to do if your abstract type became &'_ str.

#8

You can also defer the trait bounds to where you construct your wrapper, but since you’re not using a wrapper type anymore, this isn’t applicable.

1 Like