Through various studies, I learned about the Rust language.
Rust Learning: Why Rust Exists
1. What Problem Does It Solve?
First, languages with GC (garbage collection) don’t have the most extreme performance, and without GC, there’s pretty much only C and C++, but these two languages are prone to null pointer or wild pointer issues during runtime, and these cannot be checked during compilation.
In summary, what Rust needs to solve is no GC + no null pointers:
2. No GC is Easy:
Why have GC? Our program has a stack and a heap during runtime. The stack stores functions and variables, and the heap is used to store large or mutable variables. Since the resources we store on the stack are small, we can simply copy them when sharing variables. However, variables in the heap are very large, and copying them consumes a lot of performance. Therefore, we use pointers to point to the same heap resource to implement shared variables. But if multiple pointers point to the same heap variable, how should we determine when this variable should be released?
The easiest thing to think of is to release the resource when the last pointer stops pointing to it. However, this is one of the principles of GC, which means a program periodically checks whether there are pointers in the stack pointing to variables in the heap, and releases them if there are none.
Since this timing is difficult to grasp, and we must implement variable sharing (a programming language without variable sharing is meaningless), we can set that when a new pointer points to it (usually the original pointer is assigned to the new pointer), we consider that “ownership” has been transferred, which means the original pointer is invalid and the new pointer prevails, and the resource is released when our pointer leaves the scope.
But in this case, if we need to pass parameters in different scopes, won’t it immediately become invalid? Therefore, we introduced “borrowing” (which is actually a reference), which means using the space on the heap to point to our heap pointer that we currently own. In this way, since the borrow does not directly point to the heap space, it will not release heap resources.
However, if multiple borrows can modify the heap space, it’s like reading and writing at the same time, which can lead to memory corruption. Therefore, we set “mutable borrow” and “immutable borrow”. A mutable borrow is like a write lock, and there can only be one mutable borrow at the same time, and there cannot be an immutable borrow when there is a mutable borrow. If it is an immutable borrow, there can be multiple immutable borrows at the same time.
So far, “no GC” has been implemented.
3. Next is the Null Pointer Problem:
For this problem, we introduce “lifetime”, which means the lifetime of our borrow cannot be longer than the lifetime of the original borrowed object. The compiler will check this for us, but in some cases, the compiler cannot intelligently identify it. For example, if I pass in multiple borrows and then return different borrows based on some conditions, the compiler does not know the function logic. It only knows that the returned one is related to the two passed in, but the compiler does not know how they are related, so we need to manually annotate it. (When leaving the scope, the lifetime ends)
4. Summary
For these beginner concepts, I think the most difficult thing to get started with Rust is “lifetime”. However, I think we can first write functions and then annotate them by associating them with which ones, or directly let the AI assistant help us annotate based on the context.
5. Special Supplement
Rust has String, &str, and &String:
Stringis easy to understand as a mutable string, occupying 24 bytes on the stack, storing the heap address pointer, capacity, and length respectively.&Stringis also easy to understand, occupying 8 bytes on the stack, storing the stack address of theStringit points to.&strneeds to be well understood, it can be understood as an immutable array, occupying 16 bytes on the stack, storing the heap address pointer and length respectively.
When a variable has a borrow, and the borrow’s lifetime has not ended, ownership cannot be transferred:
|
|
When a variable has an immutable borrow, and it has not expired, the variable with the original ownership cannot be changed (it is frozen):
|
|
When a variable has a mutable borrow, and it has not expired, the variable with the original ownership cannot be read or written:
|
|
With a mutable borrow, there cannot be an immutable borrow, and vice versa, mutable cannot be downgraded to immutable:
|
|