From and Into
Let's go back to where our string journey started:
let ticket = Ticket::new(
"A title".into(),
"A description".into(),
"To-Do".into()
);
We now know enough to start unpacking what .into() is doing here.
The problem
This is the signature of the new method:
impl Ticket {
pub fn new(
title: String,
description: String,
status: String
) -> Self {
// [...]
}
}
We've also seen that string literals (such as "A title") are of type &str.
We have a type mismatch here: a String is expected, but we have a &str.
No magical coercion will come to save us this time; we need to perform a conversion.
From and Into
The Rust standard library defines two traits for infallible conversions: From and Into,
in the std::convert module.
pub trait From<T>: Sized {
fn from(value: T) -> Self;
}
pub trait Into<T>: Sized {
fn into(self) -> T;
}
These trait definitions showcase a few concepts that we haven't seen before: supertraits and implicit trait bounds. Let's unpack those first.
Supertrait / Subtrait
The From: Sized syntax implies that From is a subtrait of Sized: any type that
implements From must also implement Sized.
Alternatively, you could say that Sized is a supertrait of From.
Implicit trait bounds
Every time you have a generic type parameter, the compiler implicitly assumes that it's Sized.
For example:
pub struct Foo<T> {
inner: T,
}
is actually equivalent to:
pub struct Foo<T: Sized>
{
inner: T,
}
In the case of From<T>, the trait definition is equivalent to:
pub trait From<T: Sized>: Sized {
fn from(value: T) -> Self;
}
In other words, both T and the type implementing From<T> must be Sized, even
though the former bound is implicit.
Negative trait bounds
You can opt out of the implicit Sized bound with a negative trait bound:
pub struct Foo<T: ?Sized> {
// ^^^^^^^
// This is a negative trait bound
inner: T,
}
This syntax reads as "T may or may not be Sized", and it allows you to
bind T to a DST (e.g. Foo<str>). It is a special case, though: negative trait bounds are exclusive to Sized,
you can't use them with other traits.
&str to String
In std's documentation
you can see which std types implement the From trait.
You'll find that String implements From<&str> for String. Thus, we can write:
let title = String::from("A title");
We've been primarily using .into(), though.
If you check out the implementors of Into
you won't find Into<String> for &str. What's going on?
From and Into are dual traits.
In particular, Into is implemented for any type that implements From using a blanket implementation:
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
If a type U implements From<T>, then Into<U> for T is automatically implemented. That's why
we can write let title = "A title".into();.
.into()
Every time you see .into(), you're witnessing a conversion between types.
What's the target type, though?
In most cases, the target type is either:
- Specified by the signature of a function/method (e.g.
Ticket::newin our example above) - Specified in the variable declaration with a type annotation (e.g.
let title: String = "A title".into();)
.into() will work out of the box as long as the compiler can infer the target type from the context without ambiguity.
Exercise
The exercise for this section is located in 04_traits/09_from