* Render the book in PDF using `pandoc` and LaTeX. * Fix installs. * Go the apt-get route * Another attempt * Avoid installing twice. * Re-order. * Add more packages. * Minimise deps. Fix link checker. * Missing package. * Missing package. * Missing package. * More packages. * Missing package. * Missing package. * More packages... * Remove. * Fix link checker. * Fix link checker. * Fix path. * Add subtitle. * Avoid running over the right margin. * Avoid running over the right margin. * Formatting
3.7 KiB
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.