TL;DR
Why does this code work?
use std::fmt;
fn takeaslice<T: fmt::Debug>(slice: &[T]) -> &[T] {
dbg!(&slice);
slice
}
fn main() {
let v = Vec::from([1,2,3]);
takeaslice(&v);
}
Output:
cocoa@TheTreehouse /t/tmp.mcEOkAauWM (main)> cargo run (self)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/what`
[src/main.rs:4:5] &slice = [
1,
2,
3,
]
The function takeaslice
should only take a slice of a generic type T
, but somehow it can take Vec<T>
? I can't tell why they're interchangable. One is stack allocated and statically sized, the other is heap allocated and growable.
While trying to understand the difference between Vec::iter
and Vec::into_iter
, I decided to look at the definitions of the functions. Vec::iter
is defined in the impl Deref
block and Vec::into_iter
is defined in the impl IntoIterator
blocks.
The IntoIterator
trait defines how a type is to be transformed into an iterator, so Vec::into_iter
returns a IntoIter<T, A>
struct, which itself implements the Iterator
trait (phew). It's implementation in the source code looks like this.
impl<T, A: Allocator> IntoIterator for Vec<T, A> {
type Item = T;
type IntoIter = IntoIter<T, A>;
/// Creates a consuming iterator, that is, one that moves each value out of
/// the vector (from start to end). The vector cannot be used after calling
/// this.
///
/// # Examples
///
/// ```
/// let v = vec!["a".to_string(), "b".to_string()];
/// let mut v_iter = v.into_iter();
///
/// let first_element: Option<String> = v_iter.next();
///
/// assert_eq!(first_element, Some("a".to_string()));
/// assert_eq!(v_iter.next(), Some("b".to_string()));
/// assert_eq!(v_iter.next(), None);
/// ```
#[inline]
fn into_iter(self) -> Self::IntoIter {
unsafe {
let me = ManuallyDrop::new(self);
let alloc = ManuallyDrop::new(ptr::read(me.allocator()));
let buf = me.buf.non_null();
let begin = buf.as_ptr();
let end = if T::IS_ZST {
begin.wrapping_byte_add(me.len())
} else {
begin.add(me.len()) as *const T
};
let cap = me.buf.capacity();
IntoIter { buf, phantom: PhantomData, cap, alloc, ptr: buf, end }
}
}
}
This mostly makes sense, without knowing how all of that works, the idea is just to have pointers to the start and end of the block of memory one is iterating over. Then, since IntoIter
implements the Iterator
trait, we can use it as an iterator (right?).
Now coming to what Vec::iter
is doing, when I ctrl+F the source code for the vec module to find fn iter(
nothing comes up, but it is listed in the docs, so I clicked the source
button in the docs and it sends me to the source code for the slice
module(?). Here's what it links me to.
/// Returns an iterator over the slice.
///
/// The iterator yields all items from start to end.
///
/// # Examples
///
/// ```
/// let x = &[1, 2, 4];
/// let mut iterator = x.iter();
///
/// assert_eq!(iterator.next(), Some(&1));
/// assert_eq!(iterator.next(), Some(&2));
/// assert_eq!(iterator.next(), Some(&4));
/// assert_eq!(iterator.next(), None);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg_attr(not(test), rustc_diagnostic_item = "slice_iter")]
pub fn iter(&self) -> Iter<'_, T> {
Iter::new(self)
}
Okay, then that must mean it's calling Iter::new
(somehow. even though it's not in the impl for vec!!!) on the vector. This is what the source for Iter::new
looks like.
impl<'a, T> Iter<'a, T> {
#[inline]
pub(super) fn new(slice: &'a [T]) -> Self {
let len = slice.len();
let ptr: NonNull<T> = NonNull::from(slice).cast();
// SAFETY: Similar to `IterMut::new`.
unsafe {
let end_or_len =
if T::IS_ZST { without_provenance(len) } else { ptr.as_ptr().add(len) };
Self { ptr, end_or_len, _marker: PhantomData }
}
}
Now I think it's the same or somewhat similar idea here? Pointer to the start to the end or length of the data. The rest I assume the Iter type can handle. My questions are:
- How is
.iter
implemented forVec
even though it's not in any impl block for it? It's in theimpl<T> [T] {...}
block. - How does the
Iter::new
method take in a slice when it's a different struct? What qualifies a struct to be treated as a slice?
I didn't find any answers in the internals of rust. The source code for Vec
just wraps the data in a RawVec
which wraps RawVecInner
(and PhantomData which idk what that is).
struct RawVecInner<A: Allocator = Global> {
ptr: Unique<u8>,
/// Never used for ZSTs; it's `capacity()`'s responsibility to return usize::MAX in that case.
///
/// # Safety
///
/// `cap` must be in the `0..=isize::MAX` range.
cap: Cap,
alloc: A,
}
This just seems like a unique pointer to a bunch of bytes and the capacity of the allocated vector. I even tested it separately if Vec<i32>
can be passed to a function that takes a reference to a slice.
- Why is there both
.iter
and.into_iter
? Both of them return structs that implement anIterator
trait, the former treating the data as an array slice and the latter yielding references to heap memory. I don't understand when one would implement the former or the latter.
Thank you for reading! Any help is appreciated