Is there a way to make this functionality without unsafe

good day,

im wondering if there is a way to make this kind of functionality without using unsafe.?

#[derive(Debug)]
struct Test {
    val: RefCell<usize>,
}

#[derive(Debug)]
struct Test2<'a> {
    val: &'a *mut usize,
}

fn test() {
    let mut t1 = Test {
        val: RefCell::new(1),
    };

    let t2 = Test2 {
        val: &t1.val.as_ptr(),
    };

    unsafe {
        assert_eq!(**t2.val, 1);
    }

    t1.val = 2.into();

    unsafe {
        assert_eq!(**t2.val, 2);
    }
}

I have no idea what exactly is the question. What functionality?

1 Like

It's hard to tell what you want here, since your code has undefined behavior, but maybe something like this?

#[derive(Debug)]
struct Test {
    val: Cell<usize>,
}

#[derive(Debug)]
struct Test2<'a> {
    val: &'a Cell<usize>,
}

fn test() {
    let mut t1 = Test {
        val: Cell::new(1),
    };

    let t2 = Test2 {
        val: &t1.val,
    };

    assert_eq!(t2.val.get(), 1);

    t1.val.set(2);

    assert_eq!(t2.val.get(), 2);
}
1 Like

The functionality of this program is undefined behavior[1], so naturally there is no way to obtain this functionality without unsafe.

error: Undefined Behavior: trying to retag from <2824> for SharedReadOnly permission at alloc1506[0x8], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:28:9
   |
28 |         assert_eq!(**t2.val, 2);
   |         ^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         trying to retag from <2824> for SharedReadOnly permission at alloc1506[0x8], but that tag does not exist in the borrow stack for this location
   |         this error occurs as part of retag at alloc1506[0x8..0x10]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <2824> was created by a SharedReadWrite retag at offsets [0x8..0x10]
  --> src/main.rs:18:15
   |
18 |         val: &t1.val.as_ptr(),
   |               ^^^^^^^^^^^^^^^
help: <2824> was later invalidated at offsets [0x0..0x10] by a write access
  --> src/main.rs:25:5
   |
25 |     t1.val = 2.into();
   |     ^^^^^^^^^^^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `main` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:38:16: 38:22
   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Usually, “functionality” is best expressed via some API surface, and not just via a single test case. The “functionality” of a single test run of assertions, besides UB, usually only can be "the program panics" or "the program doesn't panic", the latter can be achieved just as well by a program that does nothing.

There is no apparent API surface im this program, the test case directly interacts with a raw pointer. If directly interacting with a raw pointer (in a way that also dereference it) is supposed to be the “functionality” of this program, then that's also a thing that can't be done without unsafe.

Lastly, the usage of the shared reference is confusing. Why is &'a *mut usize being used instead or *mut usize directly? Confusing implementation details like this make it even more impossible to gauss the intent of this code.


  1. well... at least under the “stacked borrows” rules, which might be more strict than what Rust eventually settles on, but for now it's a good idea to avoid breaking them nonetheless ↩︎

Dereferencing a raw pointer always requires unsafe.

Overwriting the RefCell invalidates the *mut. (Run Miri under Tools.)

Miri doesn't complain about this though.

-    t1.val = 2.into();
+    t1.val.swap(&RefCell::new(2));
1 Like

thx for the reply,

i figured the example made it pretty clear.. but i was clearly mistaking.
@geeklint hit the nail though.

Thx this was exactly what i was aiming for!

im not sure what you mean by undefined behavior though..
it seems to compile.. maybe it my naming convention that is throwing ppl off here..
this is just a test function, not a actual test.

but this was exactly what i needed thank you :partying_face:

@steffahn
i used this as a example .. not as match as a program or a test case,
the functionality i was referring to was having a ref in one struct of the value in a other struct, and if the original value would change the second struct with the ref would change with it,
as this is not possible without smart pointers, i used RefCell because because it made the idea work, im sorry that my example was not good in explaining what i was after.
i will try to be more clear in the future ..

not sure where you get these errors from though is it compiles normally

@quinedot
Sorry bad explanation from my part..

That's a great description that makes much more clear what you were after!

So "undefined behavior" is a (slightly weird) terminology for "doing forbidden things in unsafe code". Unsafe code is so — well – unsafe and (potentially) dangerous because you need to manually pay attention to rules that are not enforced by the compiler (or any runtime checks) automatically. There exists a very useful tool called miri though, that aids with discovering many cases of violating the rules of unsafe code. It can be installed (see its repo to find instructions), or it's also available on the playground (under "Tools") which is where I got this error message from :wink:

Undefined behavior (UB) means your program does something which is not defined by the language, and thus if it still compiles, it can compile to garbage machine code which might do anything at all. "Anything at all" includes "seems to run ok", but you can't count on that (a) happening in the first place, and (b) continuing to happen in the future if it happens today.

It's a concept in languages other than Rust, and it's the source of a great many observable and exploitable software bugs. Any program with UB is buggy, even if it happens to compile down to something not observable or exploitable today.

The only way to get UB in Rust, by design, is if someone used unsafe incorrectly [1]. The unsafe boundary exists so that you can write purely safe code and know it cannot cause UB (we say that it is sound).

By using unsafe, you take on the responsibility of ensuring it's sound (can't result in UB). So you should understand how to avoid UB if you use unsafe.


  1. or a bug in the compiler ↩︎

1 Like

The definition of the rust language does not prescribe a meaning to the code you wrote, like how the English phrase "Colorless green ideas sleep furiously" is syntactically valid ("compiles") but doesn't mean anything.

I recommend avoiding unsafe code (which is the only way in Rust to have undefined behavior) until you have more background in the language semantics.

1 Like

@steffahn
i see i will take a look at that thx!.

@quinedot @geeklint
yeh that makes sense this is kind of why i asked for a way to not use unsafe and have this functionality,
its the first bit of unsafe i have used sins learning rust.. but i needed this kind of functionality the one i just explained,

Blockquote
the functionality i was referring to was having a ref in one struct of the value in a other struct, and if the original value would change the second struct with the ref would change with it,

will do for sure!
i just used unsafe as it was the only way i could get the idea working that i was after..

thx to all of you for the quick replays!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.