I'm writing a function that requires the individual digits of a larger integer to perform operations on.
I've tried the following:
fn example(num: i32) {
// I can safely unwrap because I know the chars of the string are going to be valid
let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
for digit in digits {
println!("{}", digit)
}
}
But the borrow checker says the string doesn't live long enough:
error[E0716]: temporary value dropped while borrowed
--> src/lib.rs:3:18
|
3 | let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
| ^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
4 | for digit in digits {
| ------ borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
The following does work:
let temp = num.to_string();
let digits = temp.chars().map(|d| d.to_digit(10).unwrap());
But that looks even more contrived.
Is there a better, and possibly more natural way of doing this?
But the borrow checker says the string doesn't live long enough.
That's because it doesn't. You aren't using the iterator, so the type of digits
is
std::iter::Map<std::str::Chars<'_>, <closure>>
That is, a yet-to-be-evaluated iterator that contains references to the allocated string (the unnamed lifetime '_
in Chars
). However, since that string has no owner, it is dropped at the end of the statement; before the iterator is consumed.
So, yay for Rust, it prevented a use-after-free bug!
Consuming the iterator would "solve" the problem, as the references to the allocated string would not attempt to live longer than the allocated string; they all end at the end of the statement:
let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();
If you wanted to return an iterator, you can then convert the Vec
back into an iterator:
fn digits(num: usize) -> impl Iterator<Item = u32> {
num.to_string()
.chars()
.map(|d| d.to_digit(10).unwrap())
.collect::<Vec<_>>()
.into_iter()
}
As for an alternate solution, there's the math way, stolen from the C++ question to create a vector:
fn x(n: usize) -> Vec<usize> {
fn x_inner(n: usize, xs: &mut Vec<usize>) {
if n >= 10 {
x_inner(n / 10, xs);
}
xs.push(n % 10);
}
let mut xs = Vec::new();
x_inner(n, &mut xs);
xs
}
fn main() {
let num = 42;
let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();
println!("{:?}", digits);
let digits = x(42);
println!("{:?}", digits);
}
However, you might want to add all the special case logic for negative numbers, and testing wouldn't be a bad idea.
You might also want a fancy-pants iterator version:
fn digits(mut num: usize) -> impl Iterator<Item = usize> {
let mut divisor = 1;
while num >= divisor * 10 {
divisor *= 10;
}
std::iter::from_fn(move || {
if divisor == 0 {
None
} else {
let v = num / divisor;
num %= divisor;
divisor /= 10;
Some(v)
}
})
}
Or the completely custom type:
struct Digits {
n: usize,
divisor: usize,
}
impl Digits {
fn new(n: usize) -> Self {
let mut divisor = 1;
while n >= divisor * 10 {
divisor *= 10;
}
Digits {
n: n,
divisor: divisor,
}
}
}
impl Iterator for Digits {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.divisor == 0 {
None
} else {
let v = Some(self.n / self.divisor);
self.n %= self.divisor;
self.divisor /= 10;
v
}
}
}
fn main() {
let digits: Vec<_> = Digits::new(42).collect();
println!("{:?}", digits);
}
See also: