How can I create 100 distinct labels with type &&#

2020-04-11 07:38发布

I am trying to create a graph using RefCell and Rc. I want to create 100 nodes in a loop with string labels. This is the graph representation:

struct Node {
    datum: &'static str,
    edges: Vec<Rc<RefCell<Node>>>,
}

impl Node {
    fn new(datum: &'static str) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Node {
            datum: datum,
            edges: Vec::new(),
        }))
    }
}

This is the loop I am writing to create nodes:

for i in 0..100 {
    let a = Node::new(concat!("A", i));
    let b = Node::new(concat!("A", i + 1));
    {
        let mut mut_root = a.borrow_mut();
        mut_root.edges.push(b.clone());
    }
}

This is the error I am getting:

error: expected a literal
  --> src/main.rs:17:40
   |
17 |         let a = Node::new(concat!("A", i));
   |                                        ^

error: expected a literal
  --> src/main.rs:18:40
   |
18 |         let b = Node::new(concat!("A", i + 1));
   |                                        ^^^^^

标签: rust
1条回答
趁早两清
2楼-- · 2020-04-11 08:11

You cannot.

Lifetimes are descriptive, not prescriptive: you can't create a string and say "this data has the 'static lifetime." Rust makes you write lifetime annotations like 'static so that the compiler can check your work. In the case of building a string in a runtime loop, you can't safely call it 'static because it's not. (See also How to initialize a variable with a lifetime?)

But that doesn't mean you don't have any options.

Avoiding lifetimes altogether

Possibly what you're looking for is just to avoid annotating Node with a lifetime parameter, like Node<'a>. In which case, just use String instead:

struct Node {
    datum: String,
    edges: Vec<Rc<RefCell<Node>>>,
}

This is idiomatic and most likely the best option. If you're sure you will never need String's methods after creating it, and you don't want to store its capacity, you can call String::into_boxed_str to make it a Box<str> instead. This is the same storage-wise as &'static str, but the heap storage for the str will be reclaimed when the Node is dropped.

You could even use Rc<str>, which is also the same storage as &'static str but cheaper to clone than a Box.

Making non-static data 'static by leaking

If you really want to, you can "fake" a &'static str by making a Box<str> and leaking it -- i.e. discarding the knowledge that it's a Box so that its backing memory will never be reclaimed. Since Rust 1.26, you can use Box::leak; I'll make a helper function to create the labels:

fn label(i: u32) -> &'static str {
    let s = format!("A{}", i);
    Box::leak(s.into_boxed_str())
}
for i in 0..100 {
    let a = Node::new(label(i));
    let b = Node::new(label(i+1));
    // ...
}

The main thing this gives you is that & references are Copy. But Node isn't Copy anyway, so why not just use Box?

Generating labels at or before compile time

If you need a string that's actually 'static, it has to be there at compile time. As far as I know, macro_rules macros aren't powerful enough to do this, but you can always generate code. Here's one possibility:

// This part can be generated by a build script, or in your editor:
static LABELS: [&'static str; 100] = [
    "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "A12", "A13", "A14",
    "A15", "A16", "A17", "A18", "A19", "A20", "A21", "A22", "A23", "A24", "A25", "A26", "A27",
    "A28", "A29", "A30", "A31", "A32", "A33", "A34", "A35", "A36", "A37", "A38", "A39", "A40",
    "A41", "A42", "A43", "A44", "A45", "A46", "A47", "A48", "A49", "A50", "A51", "A52", "A53",
    "A54", "A55", "A56", "A57", "A58", "A59", "A60", "A61", "A62", "A63", "A64", "A65", "A66",
    "A67", "A68", "A69", "A70", "A71", "A72", "A73", "A74", "A75", "A76", "A77", "A78", "A79",
    "A80", "A81", "A82", "A83", "A84", "A85", "A86", "A87", "A88", "A89", "A90", "A91", "A92",
    "A93", "A94", "A95", "A96", "A97", "A98", "A99", "A100",
];
for i in 0..100 {
    let a = Node::new(LABELS[i]);
    // ...
}

This might not seem like the most elegant solution, but it has several advantages over the above suggestions. The strings don't have to be allocated, so they're as cheap as possible. The code size is smaller. The resulting code is easier for the compiler to optimize. It's easy to generate LABELS with an editor macro or a one-liner in your scripting language of choice. It's also trivial to check, because it's just a list of increasing integers. The biggest reason why you would want to avoid code generation is if 100 is a number that may change and you don't want to edit multiple locations in the code when that happens. In that case runtime generation (using one of the ideas above) is probably your best bet.

查看更多
登录 后发表回答