Seems to me like C# could behind the scenes track whether or not objects need to be passed into functions and handshake on ownership and borrowing, etc. Why does explicitly declaring it help? Any examples?
The problem is called escape analysis, and as you probably guess, it's hard.
When the compiler sees even a simple function call like
foo(bar), it has to know if the
foo function keeps the
bar (owns) or borrows. Without this information in the function interface, it requires analyzing function body, and it becomes a whole-program analysis problem.
But the deal-breaker is lack of information about the intent. In Rust if you have a
Vec<String> and try to do
vec.push(&str), the compiler shows you the error right there. C# doesn't know if a vec is supposed to contain owned or borrowed values, so it doesn't know which usage is unintended. If you "blindly" happen to get all ownership and borrowing correctly, then the compiler will be able to see it, and give you efficient single-ownership memory management. But if you won't get it perfect, then the program will have ambiguous ownership, and the compiler won't know if that's a mistake, or maybe you wanted
Cow like behavior. It's like programming in Rust, but with all borrow checker errors being silent. Instead of a compilation error, you get a slower program without warning.
So in C# ownership is dynamically typed, and mixing of ownership is allowed, and the compiler doesn't know when to tell you you're mixing it up in ways that are ambiguous and require inefficient runtime checks.
Figured that Rust can detect when it's done incorrectly so therefore possible to analyze. But I guess it can only detect that because it knows that:
A) function signature agrees with calls to it
B) function body agrees with function parameters
C) whether or not variables are allowed to be passed into function.
All that can happen by checking for borrow checker, and ownership syntax.
Which can't be done in C#. Clever.. That makes sense, thanks!
Nitpicking just a little: the compiler may be able to see it. Proving single ownership in the general case is undecidable, so there will always be cases where the compiler "gets it wrong" and has to be conservative. In C# this might mean your program runs a little slower than it could. In Rust by contrast it means you're frustrated for a while until you (a) figure out a way to convince the compiler you're right, (b) agree to relax the guarantee of single ownership by choosing another type, or (c) agree to shoulder the responsibility for proving correctness yourself by using the
unsafe escape hatch.
unsafe is also important here. It can contain code that is impossible to analyze, or appears to violate ownership. In C# without such escape hatch you wouldn't be able to express special ownership of
True but in those circumstances, you could probably just handle the memory the way C# currently handles it with garbage collector and what not and I imagine it would still be pretty rare.
But having to track every single variable as it floes through every single function down to the lowest level functions from every single place to determine whether to borrow the memory.. that would be way, way to slow to compile the program. I suppose maybe as a release version could be done... but still seems too crazy to be worth it. Probably talking years of compile time for a large program.