I thought I understood that, but the following does not work because
constructing the reference to the original borrows the original and so
it seems I can t use both during the invocation of a function. Am I
missing something really simple?
let str1 = ...;
let str2 = &str1;
my_func(str1, *str2);
Are you familiar with how assignment in Rust works differently than assignment in C++? Here s a simple example:
let x = "hello".to_string();
let y = x;
What do you expect the values of x and y to be after those statements execute in Rust? Answer: x has no value, y has the value "hello".to_string(). That s known in Rust as "a move"--in other words, assignment moves values from one variable to another.
In C++, if you write:
string x = "hello";
string y = x;
then C++ will copy "hello" into y, which will leave you with two strings in memory. You have to be aware of that difference when using assignment in Rust.
What can be confusing is that sometimes when you use assignment in Rust, the value will be copied. For instance:
let x = 10;
let y = x;
After those statements execute in Rust, both x and y have the value 10. In this case, Rust copies the value into y. For simple values, like integer, float, byte, and bool types, assignment copies the value into the new variable.
The reason the compiler won t let you write something like:
fn go(orig: String, orig_ref: &String) {
println!("{orig}");
println!("{orig_ref}");
}
fn main(){
let pig = "pig".to_string();
let pref = &pig;
go(pig, pref);
}
is because calling the function involves an assignment:
go( pig , pref);
| |
V V
fn go(orig: String, orig_ref: &String)
orig = pig orig_ref = pref
Therefore, the String that was in pig
is moved into orig
, and pig
becomes "uninitialized". That leaves pref
pointing to pig
, which is no longer valid. Rust was created to prevent C++ errors, like dangling pointers, so the compiler won t let you create a dangling pointer.
See the following for a more detailed description of how Python, C++, and Rust all take different approaches to the way assignment works:
how to reuse a struct instance with Rust
Because you seem to know C++, let s talk pointers. According to "Programming Rust (Revised 2nd edition)", a String type is a "fat" pointer: it consists of a length and a capacity, in addition to a pointer to some memory on the heap where the string is actually stored. For instance, if you write:
let pig = "hi".to_string();
then this is the situation:
The length is the actual length of the string and the capacity is how many total characters can fit in the allocated memory before Rust needs to allocate more memory for the string. Like a regular pointer, a "fat" pointer is efficient to copy because it is only three "machine words" long--no matter how much memory is allocated on the heap.
Then if you write:
let pref = &pig;
I think this is what happens:
pref
doesn t point to the memory on the heap where the string is stored, rather pref
points to the 3-word String on the stack. Subsequently, if the String in pig
is moved elsewhere, i.e. the 3-word fat pointer is copied to some other location on the stack and Rust "uninitializes" pig
, then pref
would become a dangling pointer.
Rust will automatically follow pointers to pointers to get to the actual string, and that is why when you write:
println!("{}", pref);
Rust will output the string rather than an address.