How unsafe is allowed in unsafe code?

All I need is a rust struct list_head, not the other lists.
What you are saying now to me is: Ok, I can't provide it, but there is another you can choose.

No, I don't want the others.

The nature of struct list_head is there 2 pointers reference one node at least, when you want to modify one node, then you have to get a mut reference of the node, the 3rd one.

How does rust get the 3rd mutable reference given there are 2 references existing already?

:sweat_smile: Did you even read the linked book? There's a "raw" linked list implementation near the end.

And a pointer is not a reference, they have different rules.

I don't yet.

But to my understanding, pointers can be used to produce reference. Can rust forbids such producing at some particular time?
It it can't, the raw pointers is a reference effectively.

It is forbidding that now, exactly by declaring some of such conversions UB. Or do you mean "can Rust compiler check this restriction automatically"?

2 Likes

In any case, raw pointers is introduced to code for some reason. de-referencing is one of them, and rust allows it in unsafe blocks, does not forbids it by declaring some of such conversions UB.

If you haven't read the book, don't make accusation like this.

Rust forbid that in safe code blocks, and it's your responsibility to not break the rule around references in unsafe code.

3 Likes

Well, I don't think you can implement a copy of struct list_head with safe rust. And maybe you can't implement it without breaking rules using unsafe rust.

And you say breaking rules is definitely forbidden.
I surely will not do that if I can find a way to satisfy all the rules, but for now, I can't find such a way.

I want a positive answer by giving a implementation to show my wrong.

First of all, you have wrong interpretation of the "rules". If you want rules around unsafe code, go read about stacked borrow or tree borrow linked above.

Read the linked book then?

Rust forbids you from creating references from raw pointers in safe code. In unsafe code you can do that, but by using unsafe you're making a contract with the compiler stating that you won't use that to produce UB. Creating multiple aliasing &mut references is UB and thus counts as breaking that contract.

It does "forbid" invalid dereferences in the sense that the compiler is allowed to not preserve the meaning of your code if you do so.

There are correct ways to use raw pointers and that's why they are in the language, but that doesn't mean that any use of raw pointers is correct.

Generally yes, though in some cases you could write something similar.

Here's an example implementation Rust Playground

However this is mostly useless. The given functions mostly bubble up the requirements for their body to be safe. Their safety mostly derives from how those pointers were created, which is an implementation detail not present in the C code.

For a heap allocated linked list the previously linked book has a full guided implementation Layout - Learning Rust With Entirely Too Many Linked Lists
The standard library also has an implementation of a heap allocated linked list https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs

For a stack allocated linked it becomes a bit more tricky since it's much easier to accidentally take a mutable reference. Generally the solution is to use the std::ptr::addr_of_mut! macro though.

2 Likes

It is tons to read, and I can't reply based the content in the near future.
But the code showed is not a right, because there are other threads, beginning at other nodes, and reach the node following next/prev links.

If the rules forbids these , then function can't be implemented, if allows, breaking rules.
And the rule is introducing UB is not allowed, then seems the only thing is the function can't be implented.

"Perfect language", Hmmm.... Likely there is no such thing. Perfection being what it is, unobtainable.

A language with no "unsafe" would be useless.

As soon as you want to communicate with the outside world or make use of functions from another language, say C, from your Rust code you are entering a world that the Rust compiler knows nothing about. It does not know how your hardware works or what your C code may be doing. The Rust compiler cannot verify any of that and hence to be "safe" would have to not allow it. Now you have lost your I/O and interoperability and can do nothing useful.

So we need "unsafe" to get anything useful done.

One might claim that language like Python or whatever are always safe. But I would say no. As soon as you do I/O from them you are depending on their run-time and it's interfaces to the real world, which is of course written in C/C++ or whatever, and is "unsafe".

As it happens, messing around with raw pointers is also something that cannot be rigorously checked by a compiler or other analyser. Hence the need for "unsafe" when building linked lists and such.

Please do read the article on linked lists in Rust I posted above.

If that's the case then your C code also contained data races and thus is UB. If you start with UB C code and translate it to unsafe Rust you can't expect it to not be UB too.

1 Like

Yes, maybe I find what confuses me: the rule of only one mutable reference allowed only applies to reference, not pointers.

muti *mut pointer is allowed.
Keeping this in mind, everything becomes clear.

That's where misunderstanding comes from. That's not ā€œGod languageā€. That's something that couldn't ever exist at all. And no, it's not as in ā€œwe have tried to create it, but haven't succeeded thus farā€. It's just a simple mathematical theorem which was known since before most of us were bornā€¦ yet is still ignored by some.

Nothing can provide such language. Not even GodĀ¹).

Thus, of course, Rust doesn't provide it, either. Instead Rust includes two sublanguages: safe one (where all ā€œbad thingsā€ are rejected) and unsafe one (where all ā€œgood thingsā€ are accepted).

They are introduced to make it possible to write ā€œgood thingsā€ which compiler couldn't accept in safe Rust. And then community (and not the compiler) have to ensure that people who not writing ā€œgood thingsā€ wouldn't share code with others. So far there was one prominent case of such object lesson, and I hope this would be enough.

That's perfectly understandable desire, but, once again: it's not possible.

Ā¹) Wellā€¦ God may, probably, change the whole Universe and make math theorems irrelavant to it, but let's not go that far into theology. In our universe that's impossible.

Yes, but you must ensure to not cause a conflict when two threads dereference the pointer. Doing so would (same as creating two &mut references) instantly cause UB.

From another thread:

But as has been discussed here, things are even worse: You must not only refrain from dereferencing these pointers in such way that there can be a race condition, but you also must ensure to not create multiple &mut references even if you're not using them.

I also think it's illegal to do unsafe { *ptr += 1; } when there is a mutable reference around, right?

fn main() {
    let mut x: i32 = 1;
    let r: &mut i32 = &mut x;
    let p: *mut i32 = r;
    unsafe { *p += 1 }; // I assume this is UB because there exists a mut ref?
    *r += 1;
    println!("{x}");
}

(Playground)

I'm not sure on that though.

MIRI doesn't mind that example, I'm pretty sure it's fine because p is derived from r. Using r in the last line invalidates p, but you also don't use it again so that's also not an issue with this specific code.

Isn't the compiler allowed to reorder writes? (edit: e.g. interleave the read and write of line "*r += 1" with the unsafe operation?) I would assume the order of the operations isn't known.


I mean, I would expect the code to be UB for the same reason why multipe &mut references are UB. But not sure.

Edit: I tried to find something about this in the Rust reference or std doc, but didn't find anything yet.


The following has to be UB, right:

 fn main() {
     let mut x: i32 = 1;
     let r: &mut i32 = &mut x;
     let p: *mut i32 = r;
-    unsafe { *p += 1 }; // I assume this is UB because there exists a mut ref?
+    unsafe { *&mut *p += 1 };
     *r += 1;
     println!("{x}");
 }

(Playground)

?

Facing another problem, Pin does not implement DerefMut, so, I can't make aux.mux.lock().unwrap().init_list_head(); compiled.

    pub fn new() -> Self {
        use std::ops::Deref;
        let aux = Cutex {
            hold: 0,
            mux: Mutex::new(Box::pin(ListHead::new())),
        };
        // aux.mux.lock().unwrap().init_list_head();

        let mut g: MutexGuard<Pin<Box<ListHead>>> = aux.mux.lock().unwrap();
        let head: &ListHead = g.deref();
        unsafe {
            let h = &mut *(head as *const ListHead as *mut ListHead);
            h.init_list_head();
        }

        drop(g);
        aux
    }

Can I convert the shared reference to a mutable reference in the unsafe code?
Converting a shared reference to mutable reference hit another forbidden rule with no doubt, but the shared reference is the only one, there is no race.

Does this still be considered as an UB? I know I should call the init before pin, well, I just want to know is it an UB

This might be getting off-topic but I still think this is fine; again the "stack" in stacked borrows is exactly to allow multiple &mut to the same location, as long as one is derived from the other, and considering that using the original invalidates the stack above it.

You can, after all, write essentially the same thing in safe code with reborrows.

MIRI still doesn't show an error for your playground, and this seems like exactly the sort of thing it would catch if it were UB.

I thought that aliasing rules ensure that during the existence of a mutable reference, the referenced value must only be changed through that reference (and not, e.g., through a pointer dereference).

But maybe this gets off topic indeed, and I should ask that later in a separate thread.