String
is the dynamic heap string type, like Vec
: use it when you need to own or modify your string data.
str
is an immutable1 sequence of UTF-8 bytes of dynamic length somewhere in memory. Since the size is unknown, one can only handle it behind a pointer. This means that str
most commonly2 appears as &str
: a reference to some UTF-8 data, normally called a "string slice" or just a "slice". A slice is just a view onto some data, and that data can be anywhere, e.g.
In static storage: a string literal "foo"
is a &'static str
. The data is hardcoded into the executable and loaded into memory when the program runs.
Inside a heap allocated String
: String
dereferences to a &str
view of the String
's data.
On the stack: e.g. the following creates a stack-allocated byte array, and then gets a view of that data as a &str
:
use std::str;
let x: &[u8] = &[b'a', b'b', b'c'];
let stack_str: &str = str::from_utf8(x).unwrap();
In summary, use String
if you need owned string data (like passing strings to other threads, or building them at runtime), and use &str
if you only need a view of a string.
This is identical to the relationship between a vector Vec<T>
and a slice &[T]
, and is similar to the relationship between by-value T
and by-reference &T
for general types.
1 A str
is fixed-length; you cannot write bytes beyond the end, or leave trailing invalid bytes. Since UTF-8 is a variable-width encoding, this effectively forces all str
s to be immutable in many cases. In general, mutation requires writing more or fewer bytes than there were before (e.g. replacing an a
(1 byte) with an รค
(2+ bytes) would require making more room in the str
). There are specific methods that can modify a &mut str
in place, mostly those that handle only ASCII characters, like make_ascii_uppercase
.
2 Dynamically sized types allow things like Rc<str>
for a sequence of reference counted UTF-8 bytes since Rust 1.2. Rust 1.21 allows easily creating these types.
TL;DR:
- The iterator returned by
into_iter
may yield any of T
, &T
or &mut T
, depending on the context.
- The iterator returned by
iter
will yield &T
, by convention.
- The iterator returned by
iter_mut
will yield &mut T
, by convention.
The first question is: "What is into_iter
?"
into_iter
comes from the IntoIterator
trait:
pub trait IntoIterator
where
<Self::IntoIter as Iterator>::Item == Self::Item,
{
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
You implement this trait when you want to specify how a particular type is to be converted into an iterator. Most notably, if a type implements IntoIterator
it can be used in a for
loop.
For example, Vec
implements IntoIterator
... thrice!
impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>
Each variant is slightly different.
This one consumes the Vec
and its iterator yields values (T
directly):
impl<T> IntoIterator for Vec<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(mut self) -> IntoIter<T> { /* ... */ }
}
The other two take the vector by reference (don't be fooled by the signature of into_iter(self)
because self
is a reference in both cases) and their iterators will produce references to the elements inside Vec
.
This one yields immutable references:
impl<'a, T> IntoIterator for &'a Vec<T> {
type Item = &'a T;
type IntoIter = slice::Iter<'a, T>;
fn into_iter(self) -> slice::Iter<'a, T> { /* ... */ }
}
While this one yields mutable references:
impl<'a, T> IntoIterator for &'a mut Vec<T> {
type Item = &'a mut T;
type IntoIter = slice::IterMut<'a, T>;
fn into_iter(self) -> slice::IterMut<'a, T> { /* ... */ }
}
So:
What is the difference between iter
and into_iter
?
into_iter
is a generic method to obtain an iterator, whether this iterator yields values, immutable references or mutable references is context dependent and can sometimes be surprising.
iter
and iter_mut
are ad-hoc methods. Their return type is therefore independent of the context, and will conventionally be iterators yielding immutable references and mutable references, respectively.
The author of the Rust by Example post illustrates the surprise coming from the dependence on the context (i.e., the type) on which into_iter
is called, and is also compounding the problem by using the fact that:
IntoIterator
is not implemented for [T; N]
, only for &[T; N]
and &mut [T; N]
-- it will be for Rust 2021.
- When a method is not implemented for a value, it is automatically searched for references to that value instead
which is very surprising for into_iter
since all types (except [T; N]
) implement it for all 3 variations (value and references).
Arrays implement IntoIterator
(in such a surprising fashion) to make it possible to iterate over references to them in for
loops.
As of Rust 1.51, it's possible for the array to implement an iterator that yields values (via array::IntoIter
), but the existing implementation of IntoIterator
that automatically references makes it hard to implement by-value iteration via IntoIterator
.
Best Solution
I've found it useful to let the compiler guide me:
Compiling gives:
Following the compiler's suggestion and copy-pasting that as my return type (with a little cleanup):
The problem is that you cannot return a trait like
Iterator
because a trait doesn't have a size. That means that Rust doesn't know how much space to allocate for the type. You cannot return a reference to a local variable, either, so returning&dyn Iterator
is a non-starter.Impl trait
As of Rust 1.26, you can use
impl trait
:There are restrictions on how this can be used. You can only return a single type (no conditionals!) and it must be used on a free function or an inherent implementation.
Boxed
If you don't mind losing a little bit of efficiency, you can return a
Box<dyn Iterator>
:This is the primary option that allows for dynamic dispatch. That is, the exact implementation of the code is decided at run-time, rather than compile-time. That means this is suitable for cases where you need to return more than one concrete type of iterator based on a condition.
Newtype
Type alias
As pointed out by reem
Dealing with closures
When
impl Trait
isn't available for use, closures make things more complicated. Closures create anonymous types and these cannot be named in the return type:In certain cases, these closures can be replaced with functions, which can be named:
And following the above advice:
Dealing with conditionals
If you need to conditionally choose an iterator, refer to Conditionally iterate over one of several possible iterators.