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.