Achim
Achim

Reputation: 11

allocating data structures while making the borrow checker happy (second try)

As a follow up to my question from yesterday (allocating data structures while making the borrow checker happy), here is a simplified case. I would like to create a data structure and return both it and a reference to it. The following code does not compile:

fn create() -> (String, &str) {
  let s = String::new();
  let r = &s;
  return (s,r);
}

fn f() {
  let (s,r) = create();
  do_something(r);
}

If I restructure my code as follows, everything works fine:

fn create<'a>(s : &'a String) -> &'a str {
  let r = &s;
  return r;
}

fn f() {
  let s = String::new();
  let r = create(&s);
  do_something(r);
}

Unfortunately, in some cases structuring the code as in the first example is much preferable. Is there some way to make rust accept the first version?

Since both versions of the code do exactly the same thing, there shouldn't be any problems with safety. The question is just whether rust's type system is powerful enough to express it.

Upvotes: 0

Views: 59

Answers (2)

virchau13
virchau13

Reputation: 1448

The Rust rules on references are that an object can either have

  • any amount of immutable references,
  • or exactly one mutable reference.

In other words, you cannot have an immutable reference and a mutable reference to the same object at the same time. Since the object counts as a mutable reference to itself, you cannot modify the object while an immutable reference to the object is present.

This is why your first piece of code does not compile. When you return (String, &str), String contains a mutable reference to a byte array, and &str contains an immutable reference to the same byte array. It violates the borrow checker rules.

However, you may ask, how does the second piece of code work? It works because you never attempt to modify s. If you modify s after r is created, then it will fail to compile:

fn f() {
    let mut s = String::new();
    let r = &s;
    s += "modified"; // ERROR: cannot borrow `s` as mutable because it is also borrowed as immutable
    do_something(r); 
}

You shouldn't need to return a data structure and a reference to itself. Simply invoke the reference when it is necessary, rather than storing it in a variable. This will make sure that the borrow checker doesn't complain about references colliding.

fn f() {
    let s = String::new();
    // If a function takes &str, passing &String will be implicitly converted to &str
    do_something(&s);
    s += "modified"; // Perfectly fine, will still compile
    do_something_else(&s);
}

If you want to learn more about why the &String => &str conversion works, see the std::convert::AsRef trait.

Upvotes: 1

Achim
Achim

Reputation: 11

I found a solution: use a raw pointer instead of a reference. The following code works:

fn create() -> (String, *const u8) {
  let s = String::new();
  let r = s.as_ptr();
  return (s,r);
}

fn f() {
  let (s,r) = create();
  do_something(r);
}

Upvotes: 0

Related Questions