We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 11 of 24
From, Into, TryFrom: conversions that cannot lie
Convert each i64 in a slice to a u8 (an unsigned byte, 0 through 255),
and report None for any value that does not fit.
pub fn to_bytes(values: &[i64]) -> Vec<Option<u8>>
The contract
- The output has the same length and order as the input: element
iof the result describes elementiofvalues. - A value
vwith0 <= v <= 255becomesSome(v as u8), that is, the same number as au8. - Every other value (any negative number, and anything from
256up toi64::MAX) becomesNone. - An empty slice gives an empty
Vec.
Example: [0, 255, 256, -1] becomes [Some(0), Some(255), None, None]. The
test harness prints Some(n) as the number n and None as JSON null, so
the expected output is written {"value": [0, 255, null, null]}.
The starter code compiles and passes Clippy, but it converts with as, so it
returns [0, 255, 0, 255] for that example. The rest of this page explains
why those two wrong answers look plausible and how the standard conversion
traits avoid them.
What as does with an integer that does not fit
Casting one integer type to another with as never fails. When the target
type is narrower, as keeps only the low bits of the two's-complement value
and discards the rest. For a u8 target that means keeping the value modulo
256:
256 as u8is0, because 256 is1_0000_0000in binary and the low eight bits are all zero.300 as u8is44, because 300 - 256 = 44.-1i64 as u8is255, because-1is all one bits in two's complement.
The result is an ordinary u8. Nothing in its type records that information
was lost.
From, Into and TryFrom
These three traits from the standard library describe conversions between types. Simplified, the first and third look like this:
trait From<T> { fn from(value: T) -> Self; }
trait TryFrom<T> { type Error; fn try_from(value: T) -> Result<Self, Self::Error>; }
From<T>is for conversions that cannot fail.i64::from(200u8)exists because everyu8value is also a validi64. There is noimpl From<i64> for u8, sou8::from(5i64)does not compile (error E0277, "the trait boundu8: From<i64>is not satisfied").Into<U>is the same conversion written from the source side. The standard library has a blanket implementation: wheneverU: From<T>, it also providesT: Into<U>. That is why the usual advice is to implementFromand getIntofor free.let x: i64 = byte.into();works when the target type is known from context;i64::from(byte)names the target explicitly.TryFrom<T>is for conversions that can fail.try_fromreturns aResult:Ok(value)on success, orErr(error)describing the failure. For integer conversions the error type isstd::num::TryFromIntError, sou8::try_from(300i64)isErr(..)holding aTryFromIntError, whileu8::try_from(200i64)isOk(200). The error's exact debug text is not part of the exercise and has changed between Rust versions.
The standard library provides TryFrom between every pair of primitive
integer types, and From only where the conversion is lossless on every
supported platform. The "every platform" part matters for usize and isize,
whose width depends on the target (for example 32 bits on 32-bit targets and
64 bits on 64-bit ones). So usize::from(5u16) compiles but usize::from(5u32)
does not, even on a 64-bit machine where every u32 would fit, and you use
usize::try_from instead. In other words, a missing From impl means "not
guaranteed lossless everywhere", not necessarily "lossy here".
From Result to Option with .ok()
This exercise needs to know whether a conversion failed, not why, so the error
value can be discarded. Result::ok does exactly that:
u8::try_from(v).ok() // Result<u8, TryFromIntError> -> Option<u8>
.ok() turns Ok(x) into Some(x) and Err(_) into None. The opposite
direction, Option::ok_or(err), turns None into Err(err) when you need to
supply an error yourself.
Comparing the two approaches
| input | v as u8 |
u8::try_from(v).ok() |
|---|---|---|
0 |
0 |
Some(0) |
255 |
255 |
Some(255) |
256 |
0 |
None |
-1 |
255 |
None |
In the as column, the results for 256 and -1 are valid bytes, so a reader
of the output cannot tell them apart from correct conversions. In the
try_from column, the failures appear in the type as None.
This does not mean as is always wrong. It is the right tool when you want
the wrap-around behavior on purpose (for example, taking the low byte of a
checksum), or when the value's range has already been checked. Note also that
as from a float to an integer behaves differently from integer to integer:
it saturates, so 300.7_f64 as u8 is 255, -5.0_f64 as u8 is 0 and
f64::NAN as u8 is 0. A reasonable default is to use TryFrom when a value
might be out of range, and to use as only when you have decided what should
happen to out-of-range values.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.