Vec<&mut T> and Vec<T> mismatch in Builder Pattern

Hi, I have 2 structs, Graph and Node, Graph include Vec<Node> . Attributes can be attached to both Node and Graph. I tried to use Builder Pattern to generate Graph.

nodes_1 in code is of type Vec<Node>
nodes_2 in code is of type Vec<&mut Node>, because when I add attribute to Node, I have to change input and return type to &mut Node. So when I insert nodes_2 to with_nodes(), compiler report a type mismatch error.

I can't change code in main(). Any suggestion? Thanks!

use std::collections::HashMap;

#[derive(PartialEq,Debug)]
pub struct Node {
    node:String,
    attrs:HashMap<String,String>,
}
impl Node {
    pub fn new(n:&str) -> Self {
        Node{node:n.to_string(),attrs:HashMap::new()}
    }
    pub fn with_attrs(&mut self, attrs: &[(&str,&str)]) -> &mut Node {
        // add attrs to node
        self
    }
}

#[derive(PartialEq,Debug)]
pub struct Graph {
    pub nodes : Vec<Node>,
    pub attrs : HashMap<String,String>,
}
impl Graph {
    pub fn new() -> Self {
        Graph {nodes:Vec::new(),attrs:HashMap::new()}
    }
    pub fn with_nodes(&mut self, other_nodes : &Vec<Node>) -> &mut Graph {
        // add nodes to graph
        self
    }
}

fn main() {
    //! Don't change code in main()
    let nodes_1 = vec![Node::new("a")];
    let graph_1 = Graph::new().with_nodes(&nodes_1);

    // node_2 type is Vec<&mut Node> but with_nodes() in Graph receive Vec<Node>
    let nodes_2 = vec![Node::new("b").with_attrs(&[("color", "green")])];
    let graph_2 = Graph::new().with_nodes(&nodes_2);
}

Error

error[E0308]: mismatched types
  --> src\main.rs:39:43
   |
39 |     let graph_2 = Graph::new().with_nodes(&nodes_2);
   |                                           ^^^^^^^^ expected struct `Node`, found `&mut Node`
   |
   = note: expected reference `&Vec<Node>`
              found reference `&Vec<&mut Node>`

===== Update: It's been solved, see solution below =====

When you are creating a new Node, it is immutable (Self), but when you add attributes, the function returns a mutable reference (&mut Node). If you do not return an immutable reference then the 'builder pattern' will work, because the underlying type will not change when you add new things to it.

use std::collections::HashMap;

#[derive(PartialEq,Debug)]
pub struct Node {
    node:String,
    attrs:HashMap<String,String>,
}
impl Node {
    pub fn new(n:&str) -> Self {
        Node{node:n.to_string(),attrs:HashMap::new()}
    }
    pub fn with_attrs(self, attrs: &[(&str,&str)]) -> Node {
        // add attrs to node
        // build a new node here instead of returning self
        self
    }
}

#[derive(PartialEq,Debug)]
pub struct Graph {
    pub nodes : Vec<Node>,
    pub attrs : HashMap<String,String>,
}
impl Graph {
    pub fn new() -> Self {
        Graph {nodes:Vec::new(),attrs:HashMap::new()}
    }
    pub fn with_nodes(&mut self, other_nodes : &Vec<Node>) -> &mut Graph {
        // add nodes to graph
        self
    }
}

fn main() {
    //! Don't change code in main()
    let nodes_1 = vec![Node::new("a")];
    let graph_1 = Graph::new().with_nodes(&nodes_1);

    // node_2 type is Vec<&mut Node> but with_nodes() in Graph receive Vec<Node>
    let nodes_2 = vec![Node::new("b").with_attrs(&[("color", "green")])];
    let graph_2 = Graph::new().with_nodes(&nodes_2);
}
1 Like

Can I return *self?

impl Node {
    pub fn new(n:&str) -> Self {
        Node{node:n.to_string(),attrs:HashMap::new()}
    }
    pub fn with_attrs(&mut self, attrs: &[(&str,&str)]) -> Node {
        // add attrs to node
        *self
    }
}

&mut-returning builder methods aren't meant to be used to produce the final object, they're only meant to be chained. You can store the object somewhere first, then call methods on it:

let mut node_2 = Node::new("b");
node_2.attrs(&[("color", "green")]);
let nodes_2 = vec![node_2];

or you can add an explicit build method, preferably moving these methods to a dedicated NodeBuilder type: (signature: fn build(&self) -> Node)

let nodes_2 = vec![NodeBuilder::new("b").with_attrs(&[("color", "green")]).build()];

If you don't want a separate builder type, and want to chain methods on the same line as ::new(), and want builder methods to return &mut T, then you can call clone from the Clone trait. But this looks a bit weird (because you're allocating something, modifying it, and then making an unnecessary duplicate) and people might wonder why you aren't writing something more like the first snippet.

(granted, the NodeBuilder example is doing the exact same thing, but it looks less weird because the name build doesn't call attention to the data being copied)

let nodes_2 = vec![Node::new("b").with_attrs(&[("color", "green")]).clone()];
1 Like

A builder pattern typically either takes ownership of a type and returns the same type, or it takes a mutable reference to a type and returns a mutable reference. If you stick to one or the other, then you can chain methods with wild abandon and not change the underlying type.

In the main() function body, you create graph_1 by calling the method with_nodes referencing nodes_1. You create nodes_1 using the new() method, which returns the type. Likewise, graph_2 depends on a reference to nodes_2, but you create nodes_2 using the with_attrs method, which returns a mutable reference to a type. Either is fine, but you can't do both at the same time. You have to choose one lane.

1 Like

@ExpHP , thanks! About your 2nd method, how can I return a Node from &self without clone it?

    pub fn build(&self) -> Node {
        *self
    }
error[E0507]: cannot move out of `*self` which is behind a shared reference
  --> src\main.rs:17:9
   |
17 |         *self
   |         ^^^^^ move occurs because `*self` has type `Node`, which does not implement the `Copy` trait

You can't. You can only do that for things that implement the Copy trait. These are types that are trivially copyable, like integers, and types that use #[derive(Copy)]. You can read more about it here:

https://doc.rust-lang.org/std/marker/trait.Copy.html

If a type doesn't implement Copy (which Node cannot, due to its heap-allocated String and HashMaps), then you must use Clone (which can also be #[derive]d).

Thanks!

  1. &Vec<T> be converted to &[T] automatically. Prefer &[T] because it's more general. See "Programming Rust" Chapter 3 - String Types
  2. Pass self by value, change self, and return it as Self, avoid reference problem.
use std::collections::HashMap;
#[derive(PartialEq,Debug,Clone)]
pub struct Node {
    node:String,
    attrs:HashMap<String,String>,
}
impl Node {
    pub fn new(n:&str) -> Self {
        Node{node:n.to_string(),attrs:HashMap::new()}
    }
    pub fn with_attrs(mut self, attrs: &[(&str,&str)]) -> Self {
        // add attrs to node
        self
    }
}

#[derive(PartialEq,Debug,Default)]
pub struct Graph {
    pub nodes : Vec<Node>,
    pub attrs : HashMap<String,String>,
}
impl Graph {
    pub fn new() -> Self {
        Graph {
            nodes:Vec::new(),
            attrs:HashMap::new()
        }
    }
    pub fn with_nodes(mut self, nodes : &[Node]) -> Self {
        // add nodes to graph
        self.nodes = nodes.to_vec();
        self
    }
}

fn main() {
    let nodes_1 = vec![Node::new("a")];
    let graph_1 = Graph::new().with_nodes(&nodes_1);
    println!("{:?}", graph_1);

    let nodes_2 = vec![Node::new("b").with_attrs(&[("color", "green")])];
    let graph_2 = Graph::new().with_nodes(&nodes_2);
    println!("{:?}", graph_2);

}