Reputation: 11
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
Reputation: 1448
The Rust rules on references are that an object can either have
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
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