How to return a default value

Hi, I am learning Rust, and got stuck on a problem. Basically, I am trying to build a tree implementation. I define my tree as

enum TreeNode {
    Root(Vec<TreeNode>),
    Node(char, Vec<TreeNode>),
    Leaf(char)
}

I want to be able to iterate on the lists within the nodes, and if I get a leaf node, basically get an empty vector, so that I don't have to test everywhere for the node type. So I wrote something like

fn get_node_list(node: &TreeNode) -> &Vec<TreeNode> {
    match node {
        TreeNode::Root(l) => l,
        TreeNode::Node(c,l) => l,
        TreeNode::Leaf(v) => &Vec::new(),
    }
}

But this (rightfully) does not compile (returns a value referencing data owned by the current function). How do I do this in Rust? Am I thinking of the problem wrong?

Thanks

It's not really idiomatic to ever pass around &Vec<T>, because the only difference from &[T] is that it also knows its capacity. If you can live with a slice instead, then it should work fine to return an empty &[] in that last case. The vectors in the other cases will automatically deref to slices.

If you wanted a default reference to a more difficult type, like a HashMap, then you might create an empty one with lazy_static! that you could return a reference to.

1 Like

You need to focus on understanding ownership in Rust. https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html

&Vec::new() is a fundamental misunderstanding how references work. It would be a use-after-free bug in another language.

References are not for storing or returning newly created objects "by reference". They can't be used for this like pointers in C or C++. They are for giving a temporary access to data that has already been stored and is owned somewhere else. You can't lend an object that has no owner.

You can make the Vec in the leaf case owned by the program itself:

static EMPTY_VEC: Vec<TreeNode> = Vec::new();

…
return &EMPTY_VEC;

This will give you &'static Vec, which lives for as long as the entire program.

But instead of returning a &Vec<TreeNode>, which is a double indirection, you should return &[TreeNode] which is technically a copyable struct with a data pointer and a length. &vec_object will automatically dereference to a slice when needed.

Unlike &Vec::new(), Rust treats &[] as a constant literal, so it will automatically make you one owned by the program, so you can lend it.

Wouldn't this be equivalent to OP code (since const is kinda like #define)?

1 Like

You are correct: this compiles

static EMPTY_VEC: Vec<TreeNode> = Vec::new();

pub enum TreeNode { /* ... */ }

pub fn get_node_list(node: &TreeNode) -> &Vec<TreeNode> {
    match node {
        /* ... */
        TreeNode::Leaf(_) => &EMPTY_VEC,
    }
}

but this does not

const EMPTY_VEC: Vec<TreeNode> = Vec::new();

pub enum TreeNode { /* ... */ }

pub fn get_node_list(node: &TreeNode) -> &Vec<TreeNode> {
    match node {
        /* ... */
        TreeNode::Leaf(_) => &EMPTY_VEC,
    }
}

The relevant bit of explanation comes from The Rust Reference › Constant items (emphasis mine):

A constant item is an optionally named constant value which is not associated with a specific memory location in the program. Constants are essentially inlined wherever they are used, meaning that they are copied directly into the relevant context when used. References to the same constant are not necessarily guaranteed to refer to the same memory address.

On the other hand, from The Rust Reference › Static items:

A static item is similar to a constant, except that it represents a precise memory location in the program. All references to the static refer to the same memory location.

Furthermore, as already mentioned by @cuviper, it's better to change the return type like this

pub enum TreeNode {
    /* ... */
}

pub fn get_node_list(node: &TreeNode) -> &[TreeNode] {
    match node {
        TreeNode::Root(l) => l,
        TreeNode::Node(_, l) => l,
        TreeNode::Leaf(_) => &[],
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.