Help fixing this code


pub struct FooBar {}

pub fn foo(v: Vec<Rc<FooBar>>) -> [&FooBar] {
    let x = v.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
    x.as_slice()
}

Is there anyway to make this code compile? I want a return type of either [&FooBar] or &[&FooBar] so I can easily pattern match on it

  1. A slice, or [T] is a chunk of memory which consists of zero or more values of same type, placed directly next to each other. Its size is not known at compile time so you can't put it on stack frame(variable, return type, or a subfield of them) as Rust doesn't allow it now. So you can't return [&FooBar]. Anyway, the type of x.as_slice() itself is &[&FooBar], not [&FooBar].

  2. A reference is a view or reference to some value which is owned by someone else. Rust compiler statically check your code so if some reference actually outlive its referent, it throws compile error to prevent dangling reference. In your code, x.as_ref() returns &[&FooBar], a reference to a slice of references to FooBar. The slice is owned by x, which is a Vector or growable array on heap memory. Variables are dropped or call its destructor when their enclosing scope is closed. Vectors own its allocated heap memory and free them on drop, so your &[&FooBar] becomes dangling reference which points to already freed memory! Well, actually its just compile error as references cannot outlive their referent. Instead, you may end up to returns the vector itself.

  3. Did I mentioned the variables are dropped when its scope closed? It's not the 100% correct answer as there's an exception for it: moved out variables will not be dropped when its scope is closed. Move is one of the most important things that make Rust the Rust. See the code below.

let foo = get_something();
let bar = foo;

In the second line the value returned by get_something() is moved out from the variable foo and assigned to bar. In the machine level it's always same as single memcpy() from foo to var, but it has more meanings at compile time. First, move implies ownership transfer so if the value owns resources like heap allocation, the responsibility to free them on drop also transfers to bar. Second, as foos ownership to the value is transferred to the bar, foo doesn't have any rights about its value including to read it. So compiler will throw compile error if some code attempts to access to the moved out variable.

In addition to the assignment, passing values as function parameters also implies move. In your code, v is moved into the function foo, so it will be also dropped when the function returns. But your return type, say Vec<&FooBar>, contains references to FooBar, which is owned by v. Again, references cannot outlive its referent. To bypy this, foo can takes &Vec<Rc<FooBar>> to prevent move, or more generally &[Rc<FooBar>] which can be "auto-deref"ed from the vector.

So in the end, your foo may looks like this.

pub fn foo(v: &[Rc<FooBar>]) -> Vec<&FooBar> {
  v.iter().map(|x| x.as_ref()).collect()
}

Then how can you pattern match over Vec<&FooBar>? Again, vectors can be "auto-deref"ed to the slice.

match &foo() {
  [..] => ..
}
3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.