Trait aliases are unstable. FYI, because of this, you almost certainly will run into edge cases where the compiler isn't as helpful as we'd like it to be. If you're a beginner and not familiar with Rust, you absolutely should not be enabling any unstable features.
If the problematic middle step had been written without a trait alias:
pub enum Intermediate<T: Borrow<str> + Into<Cow<'a, str>>> {
SingleField(T),
DoubleField { first: T, second: T },
}
pub trait ToIntermediate<T> {
type Text: Borrow<str> + Into<Cow<'a, str>>;
fn to_intermediate(&self) -> Intermediate<Self::Text>;
}
you'd get the error of
error[E0261]: use of undeclared lifetime name `'a`
--> src/lib.rs:4:49
|
4 | pub enum Intermediate<T: Borrow<str> + Into<Cow<'a, str>>> {
| ^^ undeclared lifetime
|
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
|
4 | pub enum Intermediate<T: Borrow<str> + for<'a> Into<Cow<'a, str>>> {
| +++++++
help: consider introducing lifetime `'a` here
|
4 | pub enum Intermediate<'a, T: Borrow<str> + Into<Cow<'a, str>>> {
| +++
error[E0261]: use of undeclared lifetime name `'a`
--> src/lib.rs:13:39
|
13 | type Text: Borrow<str> + Into<Cow<'a, str>>;
| ^^ undeclared lifetime
|
help: consider making the bound lifetime-generic with a new `'a` lifetime
|
13 | type Text: Borrow<str> + for<'a> Into<Cow<'a, str>>;
| +++++++
help: consider introducing lifetime `'a` here
|
13 | type Text<'a>: Borrow<str> + Into<Cow<'a, str>>;
| ++++
help: consider introducing lifetime `'a` here
|
12 | pub trait ToIntermediate<'a, T> {
| +++
which I think might lead to a bit more productive of an error fix cycle.
If there's a "root" issue here, it's that std provides
impl<'a> From<String> for Cow<'a, str>
// and
impl<'a> From<&'a str> for Cow<'a, str>
// but not
impl<'a> From<&'static str> for Cow<'a, str>
// without overlapping impls, it'd be spelled
impl<'short, 'long: 'short> From<&'long str> for Cow<'short, str>
// but this would cause a lot of inference breakage
// as Cow::from("str") would have multiple candidates
A general fix might be for the compiler to synthesize more general impls when variant types are involved, or at least to note this in the "lifetime not general enough" error.
I believe the miscommunication with the compiler happened somewhere around here — the error message is about the &str
implementation, and not the String
implementation. String
satisfies the for<'a> Into<Cow<'a, str>>
bound you asked for, but &str
doesn't.
The reason that Cow<'static, str>: From<String>
holds is that the implementation is provided for impl<'a>
. This doesn't mean anything w.r.t. the String
, because String
doesn't have any lifetime parameters.
I find that subtyping is a horrible way of thinking about generic applicability. It's necessary for coercions, but what's useful for generics is the "outlives" relation and the "forall" binder. The impl is provided with impl<'a>
, and no bounds are placed on 'a
; this means that when you use the implementation, you're allowed to choose any lifetime you fancy, including 'static
.
Nowadays the impl might be written as impl From<String> for Cow<'_, str>
instead, which based on the explanation of your logic chain, I expect you would have intuited better. Many existing Rust devs are actually the other way, and prefer the named but unbound lifetime.
Generalizing like this is unfortunately not such a simple matter. It means requiring running headfirst into lifetime problems, and any time you're considering or the compiler starts suggesting "higher-ranked" lifetimes (for<'a>
) you're venturing off anything remotely close to the "simple" path. "Lifetime not general enough" is a contender for the worst and most difficult to improve error that the compiler will emit.
The general advice is two bits:
- Any time you're expressing a function signature of the shape
fn() -> for<'a> &'a T
, you want to sayfn() -> &'static T
instead. This holds for any "covariant" lifetime (most of them). - To accept either
&'static str
orString
, don't use generics. Just useCow<'static, str>
instead. (For&'a str
,Cow<'a, str>
.)