fn show(ref v:String){}
fn main(){
let s = "abc".to_string();
show(s);
println!("{s}"); // #1 borrow of moved value: `s`
}
In this example, #1 have an error. Then, as a contrast example:
fn main(){
let s = "abc".to_string();
let ref v:String = s;
println!("{s}"); // #2 Ok
}
#2 is ok. It appears to me that such two examples are identical except that, in the first example, we passed s to the parameter ref v:String of the function show, and in the second example, we passed s to the let binding ref v:String. Anyway, such two bindings both with the same form ref v:String, however, one takes ownership of s and the other does not. What causes the difference?
In the first example, ref v:String was also declared as taking a String by value if we admit ref v:String in the function's parameter takes a String by value, why should we treat them differently?
The pattern (so binding modifiers) of a function parameter doesn't change the function API, and the API takes parameters by value. Personally I'd find it surprising and fragile if things worked otherwise.
Regardless, it'd be a breaking change now - the destructor needs to run by the time the function returns. And also, the function can remove ref without breaking any caller; that ability would be taken away.
You say that the argument to show is a String. The ref v part is irrelevant as far as the calling code is concerned. So, show(s) passes s by value into show.
The body of show then does a by-ref binding to this argument, storing this &String in v. Again, that this happens does not concern the code in main at all. Also note that v is not of type String, it's of type &String because of that ref. The type specifier is essentially declaring what the type of the entire initialising expression on the right of the = is.
hypot_1 through hypot_4 have exactly the same signature. They all take exactly one argument of type (f64, f64). The use of a destructuring pattern in hypot_2, _3, and _4 is completely irrelevant to the calling code. That pattern matching happens only in the body of the function; the calling code neither sees it, nor cares about it.
The type of the binding and the type of the value that binding is derived from are not necessarily the same.
Because of the ref. That says "the value that is in this position? Take a reference to it and call it v".
That's why I've been using tuples to try and show that let doesn't create a variable binding, it lets you pattern match against a value which can include creating variable bindings to parts of that value.
let ref foo: String = String::new();
// foo is & String
// type specified after colon is the type of the value on the right side of =,
// NOT of the created binding.
the part before the semicolon is a PATTERN, and the part after is the TYPE of the pattern. it's just the PATTERN part is usually a simple identifier so it is confusing, it would be easier to understand if you use a more complex pattern. take this example, can you see the difference?
#[derive(Debug)]
struct MyString {
inner: String
}
// there's short hand sugar syntax, but here I use the full syntax
fn show1(&MyString { inner: ref inner }: &MyString){let _ = inner;}
fn show2(MyString { inner: ref inner }: MyString){let _ = inner;}
fn main(){
let s = "abc".to_string();
let s = MyString {
inner: s
};
show1(&s);
println!("{s:?}"); // totally ok
show2(s);
//println!("{s:?}"); // error: borrow of moved value: `s`
}
I just want to add, there's also the converse of the ref pattern: the & pattern, but that only works on Copy type, since you cannot move a non-Copy value out of a reference:
fn foo1(&x: &isize) {} // fine
fn foo2(&x: &String) {} // error
fn foo3(&_: &String) {} // actually fine, because `_` pattern is special
fn main() {
foo1(&42); // correct
foo1(42); // error
}
So, you meant, if ref v:String appears on the left side of an assignment, then it means v binding to value expression without taking its ownership, rather, if it appears in the parameter, it means the function takes the ownership of the corresponding argument?
After reading the OP again, I found that your question was actually more subtle than it appeared at first. Sorry for missing the point.
Rust's documentation is quite incomplete, and the Reference doesn't make this explicit, but indeed, this has to do with place expressions.
I don't see any inherent reason that require function parameters to be in value expression context. In theory, the language could add the ability for functions to receive places without it decaying into values by using a different syntax. But I think it would be quite a hard feature to implement, as it's uncomfortably close to being full-fledged pattern types.
I would consider the compile error on #1 to be a limitation of rustc and nothing more, while #2 is expected.