The ownership rules don't stop at let. Passing a value to a function moves it just like an assignment does — and that has consequences worth seeing clearly before we fix them with borrowing.
Passing moves (or copies)
When you call takes(s) with a String, ownership moves into the function. After the call, the caller no longer owns s and can't use it. If the argument is a Copy type like i32, it copies instead and the caller keeps its value — the same rule as assignment.
Returning gives ownership back
A function can hand ownership back through its return value. So the classic (clunky) pattern is: take the value, use it, return it so the caller gets it back. It works, but threading ownership in and out of every function by hand is exhausting — imagine returning a tuple just to keep a value you only wanted to read.
The drop connection
If a function takes ownership and doesn't return the value, the value is dropped when the function ends — its heap memory freed right there. Ownership and lifetime are the same story told from two angles: who owns it decides who frees it, and when.