let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;
println!("s1 is {}", s1);
I understand that the std::ops::add take ownership for i32 and others because they are not moved but copied for performance reason.
In this case, the s1 string is moved ( String do not implement the Copy trait ). This means s1 will be dropped ( free the memory ) at the end of std::ops:add.
In an addition s1 and s2 are not modified, they do not need to take ownership ( both of them )
Is there any reason why String do not implement the std::ops::add by taking an self immutable reference?
The reasoning for this is described in the docs (although it's on the implementation block, which isn't always displayed by default in Rustdoc):
This consumes the String on the left-hand side and re-uses its buffer (growing it if necessary). This is done to avoid allocating a new String and copying the entire contents on every operation, which would lead to O(n^2) running time when building an n-byte string by repeated concatenation.
So your point about neither string being modified is not true - s2 remains the same, but s1's buffer is used for the new string. If the operation took the left hand side by reference, a new string would have to be allocated for the add every time - rather than making this expensive operation the default, you can opt in to it by manually cloning the left hand string.