rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.96k stars 1.57k forks source link

Generic structs with additional fields for certain type constraints #1021

Open lilyball opened 9 years ago

lilyball commented 9 years ago

Right now generic types can implement traits if its type parameters match certain constraints. But sometimes implementing these additional traits requires additional struct fields. Since the struct field list is currently fixed, that means that the programmer has to decide between adding additional fields that are unused in most instances of the struct, or not implementing traits that it would be nice to have.

For example, I think it would be nice if std::io::Take<T> implemented std::io::Seek if T: std::io::Seek. But in order to do this, std::io::Take would need an additional field to keep track of the original limit (in order to support SeekFrom::Start). It seems very wasteful to put this extra field on all implementations of Take when most of them would never touch the field.

I'm not sure what the syntax of this should look like.

Diggsey commented 9 years ago

You should be able to get around this to some extent by using associated types in combination with the unit () type. Implement a trait with an associated type of () for types which don't need the field, and the correct type otherwise.

seanmonstar commented 9 years ago

@Diggsey associated types won't add storage for that type to a struct. The example is that you would normall want Take<R> { inner: R, limit: usize }, but might want Take<R> where R: Seek { inner: R, limit: usize, orig_limit: usize }.

Diggsey commented 9 years ago

You misunderstand me, I was describing something like this (not sure of syntax):

trait TakeHelper {
    type OrigLimit = ()
}

impl<R> TakeHelper for R where R: Seek {
    type OrigLimit = usize
}

struct Take<R> {
    inner: R,
    limit: usize,
    orig_limit: <R as TakeHelper>::OrigLimit
}
seanmonstar commented 9 years ago

Oh I see. This requires default values for associated items.

lilyball commented 9 years ago

That requires either being able to specialize trait impls, or being able to impl traits with negative trait bounds, as in

trait TakeHelper {
    type OrigLimit = ()
}

impl<R> TakeHelper for R where R: Seek {
    type OrigLimit = usize
}

// default impl, specialized by the above
impl<R> TakeHelper for R {
    type OrigLimit = ()
}

// alternatively, negative trait bound
impl<R> TakeHelper for R where R: !Seek {
    type OrigLimit = ()
}
luser commented 7 years ago

I implemented something like this in a patch to lru-cache, but it's not terribly nice. I made the LRUCache type generic over a CountableMeter trait, which has an associated type that can be () for the default case where it doesn't need to store an additional count. It definitely required jumping through more hoops than I had hoped.