I have read somewhere that in a language that features pointers, it is not possible for the compiler to decide fully at compile time whether all pointers are used correctly and/or are valid (refer to an alive object) for various reasons, since that would essentially constitute solving the halting problem. That is not surprising, intuitively, because in this case, we would be able to infer the runtime behavior of a program during compile-time, similarly to what's stated in this related question.
However, from what I can tell, the Rust language requires that pointer checking be done entirely at compile time (there's no undefined behavior related to pointers, "safe" pointers at least, and there's no "invalid pointer" or "null pointer" runtime exception either).
Assuming that the Rust compiler doesn't solve the halting problem, where does the fallacy lie?
Is it the case that pointer checking isn't done entirely at compile-time, and Rust's smart pointers still introduce some runtime overhead compared to, say, raw pointers in C?
Or is it possible that the Rust compiler can't make fully correct decisions, and it sometimes needs to Just Trust The Programmer™, probably using one of the lifetime annotations (the ones with the <'lifetime_ident> syntax)? In this case, does this mean that the pointer/memory safety guarantee is not 100%, and still relies on the programmer writing correct code?
Another possibility is that Rust pointers are non-"universal" or restricted in some sense, so that the compiler can infer their properties entirely during compile-time, but they are not as useful as e. g. raw pointers in C or smart pointers in C++.
Or maybe it is something completely different and I'm misinterpreting one or more of { "pointer", "safety", "guaranteed", "compile-time" }.
Disclaimer: I'm in a bit of a hurry, so this is a bit meandering. Feel free to clean it up.
The One Sneaky Trick That Language Designers Hate™ is basically this: Rust can only reason about the 'static lifetime (used for global variables and other whole-program lifetime things) and the lifetime of stack (i.e. local) variables: it cannot express or reason about the lifetime of heap allocations.
This means a few things. First of all, all of the library types that deal with heap allocations (i.e. Box<T>, Rc<T>, Arc<T>) all own the thing they point to. As a result, they don't actually need lifetimes in order to exist.
Where you do need lifetimes is when you're accessing the contents of a smart pointer. For example:
let mut x: Box<i32> = box 0;
*x = 42;
What is happening behind the scenes on that second line is this:
{
let box_ref: &mut Box<i32> = &mut x;
let heap_ref: &mut i32 = box_ref.deref_mut();
*heap_ref = 42;
}
In other words, because Box isn't magic, we have to tell the compiler how to turn it into a regular, run of the mill borrowed pointer. This is what the Deref and DerefMut traits are for. This raises the question: what, exactly, is the lifetime of heap_ref?
The answer to this is in the definition of DerefMut (from memory because I'm in a hurry):
trait DerefMut {
type Target;
fn deref_mut<'a>(&'a mut self) -> &'a mut Target;
}
Like I said before, Rust absolutely cannot talk about "heap lifetimes". Instead, it has to tie the lifetime of the heap-allocated i32 to the only other lifetime it has on hand: the lifetime of the Box.
What this means is that "complicated" things don't have an expressible lifetime, and thus have to own the thing they manage. When you convert a complicated smart pointer/handle into a simple borrowed pointer, that is the moment that you have to introduce a lifetime, and you usually just use the lifetime of the handle itself.
Actually, I should clarify: by "lifetime of the handle", I really mean "the lifetime of the variable in which the handle is currently being stored": lifetimes are really for storage, not for values. This is typically why newcomers to Rust get tripped up when they can't work out why they can't do something like:
fn thingy<'a>() -> (Box<i32>, &'a i32) {
let x = box 1701;
(x, &x)
}
"But... I know that the box will continue to live on, why does the compiler say it doesn't?!" Because Rust can't reason about heap lifetimes and must resort to tying the lifetime of &x to the variable x, not the heap allocation it happens to point to.
Is it the case that pointer checking isn't done entirely at compile-time, and Rust's smart pointers still introduce some runtime overhead compared to, say, raw pointers in C?
There are special runtime-checks for things that can't be checked at compile time. These are usually found in the cell crate. But in general, Rust checks everything at compile time and should produce the same code as you would in C (if your C-code isn't doing undefined stuff).
Or is it possible that the Rust compiler can't make fully correct decisions, and it sometimes needs to Just Trust The Programmer™, probably using one of the lifetime annotations (the ones with the <'lifetime_ident> syntax)? In this case, does this mean that the pointer/memory safety guarantee is not 100%, and still relies on the programmer writing correct code?
If the compiler cannot make the correct decision you get a compile time error telling you that the compiler cannot verify what you are doing. This might also restrict you from stuff you know is correct, but the compiler doesn't. You can always go to unsafe code in that case. But as you correctly assumed, then the compiler relies partly on the programmer.
The compiler checks the function's implementation, to see if it does exactly what the lifetimes say it does. Then, at the call-site of the function, it checks if the programmer uses the function correctly. This is similar to type-checking. A C++ compiler checks if you are returning an object of the correct type. Then it checks at the call-site if the returned object is stored in a variable of the correct type. At no time can the programmer of a function break the promise (except if unsafe is used, but you can always let the compiler enforce that no unsafe is used in your project)
Rust is continuously improved. More things may get legal in Rust once the compiler becomes smarter.
Another possibility is that Rust pointers are non-"universal" or restricted in some sense, so that the compiler can infer their properties entirely during compile-time, but they are not as useful as e. g. raw pointers in C or smart pointers in C++.
There's a few things that can go wrong in C:
dangling pointers
double free
null pointers
wild pointers
These don't happen in safe Rust.
You can never have a pointer that points to an object no longer on the stack or heap. That's proven at compile time through lifetimes.
You do not have manual memory management in Rust. Use a Box to allocate your objects (similar but not equal to a unique_ptr in C++)
Again, no manual memory management. Boxes automatically free memory.
In safe Rust you can create a pointer to any location, but you cannot dereference it. Any reference you create always is bound to an object.
There's a few things that can go wrong in C++:
everything that can go wrong in C
SmartPointers only help you not forget calling free. You can still create dangling references: auto x = make_unique<int>(42);
auto& y = *x;
x.reset();
y = 99;
Rust fixes those:
see above
as long as y exists, you may not modify x. This is checked at compile time and cannot be circumvented by more levels of indirection or structs.
I have read somewhere that in a language that features pointers, it is not possible for the compiler to decide fully at compile time whether all pointers are used correctly and/or are valid (refer to an alive object) for various reasons, since that would essentially constitute solving the halting problem.
Rust doesn't prove you all pointers are used correctly. You may still write bogus programs. Rust proves that you are not using invalid pointers. Rust proves that you never have null-pointers. Rust proves that you never have two pointers to the same object, execept if all these pointers are non-mutable (const). Rust does not allow you to write any program (since that would include programs that violate memory safety). Right now Rust still prevents you from writing some useful programs, but there are plans to allow more (legal) programs to be written in safe Rust.
That is not surprising, intuitively, because in this case, we would be able to infer the runtime behavior of a program during compile-time, similarly to what's stated in this related question.
Revisiting the example in your referenced question about the halting problem:
void foo() {
if (bar() == 0) this->a = 1;
}
The above C++ code would look one of two ways in Rust:
fn foo(&mut self) {
if self.bar() == 0 {
self.a = 1;
}
}
fn foo(&mut self) {
if bar() == 0 {
self.a = 1;
}
}
For an arbitrary bar you cannot prove this, because it might access global state. Rust soon gets const functions, which can be used to compute stuff at compile-time (similar to constexpr). If bar is const, it becomes trivial to prove if self.a is set to 1 at compile-time. Other than that, without pure functions or other restrictions of the function content, you can never prove whether self.a is set to 1 or not.
Rust currently doesn't care whether your code is called or not. It cares whether the memory of self.a still exists during the assignment. self.bar() can never destroy self (except in unsafe code). Therefor self.a will always be available inside the if branch.
Most of the safety of Rust references is guaranteed by strict rules:
If you posses a const reference (&), you can clone this reference and pass it around, but not create a mutable &mut reference out of it.
If a mutable (&mut) reference to an object exists, no other reference to this object can exist.
A reference is not allowed to outlive the object it refers to, and all functions manipulating references must declare how the references from their input and output are linked, using lifetime annotations (like 'a).
So in terms of expressiveness, we are effectively more limited than when using plain raw pointers (for example, building a graph structure is not possible using only safe references), but these rules can effectively be completely checked at compile-time.
Yet, it is still possible to use raw pointers, but you have to enclose the code dealing with them in a unsafe { /* ... */ } block, telling to the compiler "Trust me, I know what I am doing here". That is what some special smart pointers do internally, such as RefCell, which allows you to have these rules checked at runtime rather than compile-time, to gain expressiveness.
Related
I'm working on a toy programming JavaScript-like language which I'm writing in Rust, and this language has a very basic mark and sweep garbage collector. I have a working prototype, and the way I implemented it is to wrap Rust objects which are going to be allocated on the GC heap inside of a Box. Then I can get a pointer to the GC'd object and wrap pointer into a dynamically-typed Value type, i.e.:
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Value
{
Int64(i64),
UInt64(u64),
HostFn(HostFn),
Fun(*mut Function),
Str(*mut String),
Nil,
}
These are the values which my bytecode interpreter works with. The full source code is here if anyone wants to see more about how the GC works and provide feedback. I'm trying to keep the implementation as simple as possible so that this will be beginner-friendly.
What I would like advice with is that working with mutable pointers like this in Rust is very unergonomic. Every time I want to dereference them, I have to wrap everything in unsafe. This means, if I want to implement string concatenation or equality, I have to wrap the whole thing in an unsafe block where I'll dereference and borrow both strings. I have to do the same thing if I want to implement function calls. That's going to result in unsafe blocks everywhere in the implementation of my interpreter.
So my question is: can we make this more ergonomic somehow? One potential idea would be to have a helper method on Value that can return a &'static mut String for example. This is essentially lying to the Rust compiler about lifetimes for the sake of convenience, because the programmer still needs to be careful that GC'd values are on the VM heap, otherwise they will be collected. Would this be safe though? The real danger would come if the Rust compiler were to reorder operations around. Advice welcome.
I get the impression that Rust is intended to be used in highly safe systems. Then I noticed that raw pointers allow arbitrary pointer arithmetic, and they can cause memory safety and security issues.
Basically, a pointer is an object that refers to another object. In most programming languages (I guess) a pointer is actually just a number that refers to a memory address. Rust's raw pointers are really just that - memory addresses. There are other pointer types in Rust (& references, Box, Rc, Arc), for which the compiler can verify that the memory is valid and contains what the program thinks it contains. This is not the case for raw pointers; they can in principle point to any memory location, regardless of the content. Refer to The Book for more details.
Raw pointers can only be dereferenced inside unsafe blocks. These blocks are a way for the programmer to tell the compiler "I know better than you that this is safe and I promise not to do anything stupid".
It is generally best to avoid raw pointers if possible because the compiler cannot reason about their validity, which makes them unsafe in general. Things that make raw pointers unsafe are the potential to...
access a NULL pointer,
access a dangling (freed or invalid) pointer,
free a pointer multiple times,
All these points boil down to dereferencing the pointer. That is, to use the memory pointed to.
However, using raw pointers without dereferencing them is perfectly safe. This has a use case in finding out if two references point to the same object:
fn is_same(a: &i32, b: &i32) -> bool {
a as *const _ == b as *const _
}
Another use case is the foreign function interface (FFI). If you wrap a C function that takes raw pointers as arguments, there is no way around providing them to the function. This is actually unsafe (as is the whole FFI business), because the function is likely to dereference the pointer. This means you are responsible for making sure the pointer is valid, stays valid, and is not freed multiple times.
Finally, raw pointers are used for optimization. For example, the slice iterator uses raw pointers as internal state. This is faster than indices because they avoid range checks during iteration. However, it is also unsafe as far as the compiler is concerned. The library author needs to pay extra attention, so using raw pointers for optimization always comes at the risk of introducing memory bugs that you normally do not have in rust.
In summary, the three main uses of raw pointers are:
"just numbers" - you never access the memory they point to.
FFI - you pass them outside Rust.
memory-mapped I/O - to trigger I/O actions you need to access hardware registers at fixed addresses.
performance - they can be faster than other options, but the compiler won't enforce safety.
As to when raw pointers should be used, the first three points are straight-forward: You will know when they apply because you have to. The last point is more subtle. As with all optimizations, only use them when the benefit outweighs the effort and risk of using them.
A counter example when not to use raw pointers is whenever the other pointer types (& references, Box, Rc, Arc) do the job.
In go I seem to have two options:
foo := Thing{}
foo.bar()
foo := &Thing{}
foo.bar()
func (self Thing) bar() {
}
func (self *Thing) bar() {
}
What's the better way to define my funcs with self Thing or with self *Thing?
Edit: this is not a duplicate of the question about methods and functions. This question has to do with Thing and &Thing and I think it's different enough to warrent it's own url.
Take a look at this item from the official FAQ:
For programmers unaccustomed to pointers, the distinction between
these two examples can be confusing, but the situation is actually
very simple. When defining a method on a type, the receiver (s in the
above examples) behaves exactly as if it were an argument to the
method. Whether to define the receiver as a value or as a pointer is
the same question, then, as whether a function argument should be a
value or a pointer. There are several considerations.
First, and most important, does the method need to modify the
receiver? If it does, the receiver must be a pointer. (Slices and maps
act as references, so their story is a little more subtle, but for
instance to change the length of a slice in a method the receiver must
still be a pointer.) In the examples above, if pointerMethod modifies
the fields of s, the caller will see those changes, but valueMethod is
called with a copy of the caller's argument (that's the definition of
passing a value), so changes it makes will be invisible to the caller.
By the way, pointer receivers are identical to the situation in Java,
although in Java the pointers are hidden under the covers; it's Go's
value receivers that are unusual.
Second is the consideration of efficiency. If the receiver is large, a
big struct for instance, it will be much cheaper to use a pointer
receiver.
Next is consistency. If some of the methods of the type must have
pointer receivers, the rest should too, so the method set is
consistent regardless of how the type is used. See the section on
method sets for details.
For types such as basic types, slices, and small structs, a value
receiver is very cheap so unless the semantics of the method requires
a pointer, a value receiver is efficient and clear.
There isn't a clear answer but they're completely different. When you don't use a pointer you 'pass by value' meaning the object you called it on will be immutable (modifying a copy), when you use the pointer you 'pass by reference'. I would say more often you use the pointer variety but it is completely situational, there is no 'better way'.
If you look at various programming frameworks/class libraries you will see many examples where the authors have deliberately chosen to do things by value or reference. For example, in C# .NET this is the fundamental difference between a struct and a class and types like Guid and DateTime were deliberately implemented as structs (value type). Again, I think the pointer is more often the better choice (if you look through .NET almost everything is a class, the reference type), but it definitely depends on what you wish to achieve with the type and/or how you want consumers/other developers to interact with it. Your may need to consider performance and concurrency (maybe you want everything to be by value so you don't have to worry about concurrent ops on a type, maybe you need a pointer because the objects memory footprint is large and copying it would make your program too slow or consumptive).
My Frama-C plug-in creates some varinfos with makeGlobalVar ~logic:true name type. These varinfos do not exist in the AST (they are placeholders for the results of calls to allocating functions in the target program, created “dynamically” during the analysis). If my plug-in takes care not to keep any strong pointer onto these varinfos, will they have a chance to be garbage-collected? Or are they registered in a data structure with strong pointers? If so, would it be possible to make that data structure weak? OCaml does not have the variety of weak data structure found in the literature for other languages, but there is nothing a periodical explicit pass to clean up empty stubs cannot fix.
Now that I think about it, I may not even have to create a varinfo. But it is a bit late to change my plug-in now. What I use of the varinfo is a name and a representation of a C type. Function makeGlobalVar offers a guarantee of unicity for the name, which is nice, I guess, as long as it does not create a strong pointer to it or to part of it in the process.
Context:
Say that you are writing a C interpreter to execute C programs that call malloc() and free(). If the target program does not have a memory leak (it frees everything it allocates and never holds too much memory), you would like the interpreter to behave the same.
If you don't explicitely register the varinfos into one of the Globals table, Frama-C won't do it for you (and in fact, if you do, you're supposed to add their declaration in the AST and vice-versa), so I guess that you are safe here. The only visible side-effect as far as the kernel is concerned should be the incrementation of the Vid counter. Note however that makeGlobalVar itself does not guarantee the unicity of the vname, but only of the vid field.
I know that pointers in Go allow mutation of a function's arguments, but wouldn't it have been simpler if they adopted just references (with appropriate const or mutable qualifiers). Now we have pointers and for some built-in types like maps and channels implicit pass by reference.
Am I missing something or are pointers in Go just an unnecessary complication?
Pointers are usefull for several reasons. Pointers allow control over memory layout (affects efficiency of CPU cache). In Go we can define a structure where all the members are in contiguous memory:
type Point struct {
x, y int
}
type LineSegment struct {
source, destination Point
}
In this case the Point structures are embedded within the LineSegment struct. But you can't always embed data directly. If you want to support structures such as binary trees or linked list, then you need to support some kind of pointer.
type TreeNode {
value int
left *TreeNode
right *TreeNode
}
Java, Python etc doesn't have this problem because it does not allow you to embed composite types, so there is no need to syntactically differentiate between embedding and pointing.
Issues with Swift/C# structs solved with Go pointers
A possible alternative to accomplish the same is to differentiate between struct and class as C# and Swift does. But this does have limitations. While you can usually specify that a function takes a struct as an inout parameter to avoid copying the struct, it doesn't allow you to store references (pointers) to structs. This means you can never treat a struct as a reference type when you find that useful e.g. to create a pool allocator (see below).
Custom Memory Allocator
Using pointers you can also create your own pool allocator (this is very simplified with lots of checks removed to just show the principle):
type TreeNode {
value int
left *TreeNode
right *TreeNode
nextFreeNode *TreeNode; // For memory allocation
}
var pool [1024]TreeNode
var firstFreeNode *TreeNode = &pool[0]
func poolAlloc() *TreeNode {
node := firstFreeNode
firstFreeNode = firstFreeNode.nextFreeNode
return node
}
func freeNode(node *TreeNode) {
node.nextFreeNode = firstFreeNode
firstFreeNode = node
}
Swap two values
Pointers also allows you to implement swap. That is swapping the values of two variables:
func swap(a *int, b *int) {
temp := *a
*a = *b
*b = temp
}
Conclusion
Java has never been able to fully replace C++ for systems programming at places such as Google, in part because performance can not be tuned to the same extend due to the lack of ability to control memory layout and usage (cache misses affect performance significantly). Go has aimed to replace C++ in many areas and thus needs to support pointers.
I really like example taken from https://www.golang-book.com/books/intro/8
func zero(x int) {
x = 0
}
func main() {
x := 5
zero(x)
fmt.Println(x) // x is still 5
}
as contrasted with
func zero(xPtr *int) {
*xPtr = 0
}
func main() {
x := 5
zero(&x)
fmt.Println(x) // x is 0
}
Go is designed to be a terse, minimalist language. It therefore started with just values and pointers. Later, by necessity, some reference types (slices, maps, and channels) were added.
The Go Programming Language : Language Design FAQ : Why are maps, slices, and channels references while arrays are values?
"There's a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use. Introducing reference types, including slices to handle the reference form of arrays, resolved these issues. Reference types add some regrettable complexity to the language but they have a large effect on usability: Go became a more productive, comfortable language when they were introduced."
Fast compilation is a major design goal of the Go programming language; that has its costs. One of the casualties appears to be the ability to mark variables (except for basic compile time constants) and parameters as immutable. It's been requested, but turned down.
golang-nuts : go language. Some feedback and doubts.
"Adding const to the type system forces it to appear everywhere, and
forces one to remove it everywhere if something changes. While there
may be some benefit to marking objects immutable in some way, we don't
think a const type qualifier is to way to go."
References cannot be reassigned, while pointers can. This alone makes pointers useful in many situations where references could not be used.
Rather than answering it in the context of “Go”, I would answer this question in the context of any language (e.g. C, C++, Go) which implements the concept of "pointers"; and the same reasoning can be applied to “Go” as well.
There are typically two memory sections where the memory allocation takes place: the Heap Memory and the Stack Memory (let’s not include “global section/memory” as it would go out of context).
Heap Memory: this is what most of the languages make use of: be it Java, C#, Python… But it comes with a penalty called the “Garbage Collection” which is a direct performance hit.
Stack Memory: variables can be allocated in the stack memory in languages like C, C++, Go, Java. Stack memory doesn’t require garbage collection; hence it is a performant alternative to the heap memory.
But there is a problem: when we allocate an object in the heap memory, we get back a “Reference” which can be passed to “multiple methods/functions” and it is through the reference, “multiple methods/functions” can read/update the same object(allocated in the heap memory) directly. Sadly, the same is not true for the stack memory; as we know whenever a stack variable is passed to a method/function, it is “passed by value”(e.g. Java) provided you have the “concept of pointers”(as in the case of C, C++, Go).
Here is where pointers come into picture. Pointes let “multiple methods/functions” read/update the data which is placed in the stack memory.
In a nutshell, “pointers” allow the use of “stack memory” instead of the heap memory in order to process variables/structures/objects by “multiple methods/functions”; hence, avoiding performance hit caused by the garbage collection mechanism.
Another reason for introducing pointers in Go could be: Go is ought to be an "Efficient System Programming Language" just like C, C++, Rust etc. and work smoothly with the system calls provided by the underlying Operating System as many of the system call APIs have pointers in their prototype.
One may argue that it can done by introducing a pointer-free layer on top of the system call interface. Yes, it can be done but having pointers would be like acting very close to the system call layer which is trait of a good System Programming Language.