-
Notifications
You must be signed in to change notification settings - Fork 118
Description
First let me say that C-STRUCT-BOUNDS
talks about derived bounds mainly, which is completely fine, just a bit confusing (as the text down below speaks about bounds in general). I'd like to see a more visible distinction between the two, but that's not my main point here.
I suggest adding a new execption: "when a struct is supposed to wrap a type implementing a specific trait" - that means specialized wrappers for Iterator
, Future
, Stream
, Read
, Write
etc. Reason: if the user attempts to instantiate the type without generic implementing the trait, he hits the error somewhere else, which is confusing and causes long error messages.
Requiring the trait will cause the error sooner, pointing at the correct location.
As an alternative, one might omit the bounds from struct itself and put them on every constructing function instead. This works until the crate author forgets about one case and exactly that case will be experienced by the user. (Murphy law: Everything that can break will break.)
#6 reasoned that it's annoying, but since generics are just type-level functions, it's equally annoying as having to write types in function signatures, which was very reasonably considered as a good thing.
Activity
Kixunil commentedon Nov 2, 2020
Let me clarify this to make the rules clearer and maybe more obvious.
Terminology:
A type is considered Trait wrapper if:
Trait
Trait
didn't existA type is multi trait wrapper if
TraitA
,TraitB
...TraitA
,TraitB
... existedBy "nobody would write it" I mean that there's no good reason to write it.
In other words, if
S<T>
is a generic type and rewriting it astype S<T> = T;
would not remove any functionality besides the traits it wraps (and constructors), then nobody would write it without those traits existing.I propose that:
Examples:
This is a combinator analogous to
iterator.filter()
. If theIterator
trait didn't exist, then this type would not provide any useful functionality. Deleting it and rewriting all occurrences toT
would not change the behavior of the program. (As the only thing that it can do isclone
and print itself. Debug impl would change but that's not really interesting/relevant.)As we can see this type also impls
Clone
andDebug
(via derive) but it'd still be useful without them existing.Multi trait wrapper example:
This type provides useful functionality if
Read
exists butWrite
does not; ifWrite
exists butRead
does not; if bothRead
andWrite
exists.If neither
Read
notWrite
exist, the type is completely useless.Making sure that
Buffer<T>
can't be constructed ifT
doesn't implRead
norWrite
makes sure user is informed about the fact at the point where it's constructed, not later where it can be confusing.However, consider this alternative:
This type implements buffering for readers only, not for writers. It additionally delegates writes if the inner type is a writer but does not buffer them.
Such type is still useless without
Read
existing because it can be replaced withT
without loss of functionality.I hope this explanation and the examples make the idea and reasons behind it more clear. I'll be happy to clarify if something is still not obvious.