Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: How to program in Rust as if it was old school C++ with pointers
24 points by scotty79 on Dec 20, 2022 | hide | past | favorite | 19 comments
Basically wrap your data struct in Option<Rc<RefCell<_>>>

Better yet, use your custom type that provides few helper functions that make using it as nice as old C++ pointers.

Obviously there's no pointer arithmetic, but it works great for dynamically allocated structures like trees or linked lists.

Please comment how it's a horrible idea that will bring doom to me and my family.

    fn main() {
        struct Data { pub number: i32 }             // struct Data { int number; };
        
        let mut p:Pointer<Data> = Pointer(None);    // Data *p = NULL;
        
        println!("{}", p.is_none());                // printf(p == NULL ? "true\n" : "false\n");
        
        p = Pointer::new(Data { number: 5 });       // p = new Data { number: 5 };
        println!("{}", p.pointed().number);         // printf("%d\n", p->number);
        
        let q = p.clone();                          // auto q = p;
        q.pointed().number = 6;                     // q->number = 6;
        println!("{}", p.pointed().number);         // printf("%d\n", p->number);
    
        println!("{}", p.is_clone_of(&q));          // printf(p == q ? "true\n" : "false\n");
    }                                               // delete p;
Helper Pointer<T> type:

    use std::{rc::Rc, cell::{RefCell, RefMut}};
    
    
    #[derive(Debug)]
    struct Pointer<T>(Option<Rc<RefCell<T>>>);
    
    impl<T> Clone for Pointer<T> {
        fn clone(&self) -> Self {
            Pointer(Some(self.0.as_ref().unwrap().clone()))
        }
    }
    
    impl<T> Pointer<T> {
        pub fn new(d: T) -> Pointer<T> {
            Pointer(Some(Rc::new(RefCell::new(d))))
        }
        pub fn pointed(&self) -> RefMut<T> {
            let res = self.0.as_ref().unwrap().as_ref().borrow_mut();
            res
        }
        pub fn is_clone_of(&self, other:&Pointer<T>) -> bool {
            Rc::ptr_eq(&self.0.as_ref().unwrap(), &other.0.as_ref().unwrap())
        }
        pub fn is_none(&self) -> bool {
            self.0.is_none()
        }
    }

Live version (Rust):

https://play.rust-lang.org/?gist=f43ce73b89537e0a9ae0586169b...

Live version (C++):

http://tpcg.io/_LNG59M



Option<Rc<RefCell<_>>> is not the same thing as a pointer, as the semantics are different, and it uses much more memory than an actual pointer.

Rust has pointers. If you want to program like old school C++, wrap everything in unsafe and use all the pointers you want.


> wrap everything in unsafe

You are expected to follow Rust's memory model in unsafe blocks. Treating unsafe Rust like C is much more dangerous than just using C.


Assuming you're talking about the aliasing rules, my understanding is that that only applies if you are mixing pointers with references. If you just use `pointer::read` `pointer::write` the aliasing rules don't apply (doesn't mean it's safe, but it won't immediately be undefined behaviour to write to a mutable pointer while another mutable pointer exists, as it would be if you aliased a mutable reference).


I don't want unsafe. I just want it to be out of my hair. RefCell is perfectly safe.


C++ compiles down to 90 lines of ASM.

https://godbolt.org/z/YYaK8f9rz

Rust compiles down (up?) to 1428!

https://godbolt.org/z/91YfzT5fb

Thanks, I hate it! Do dynamic typing next!


For that small price you get security against everything except memory leaks (which you don't get anyways in this bare C++ either) not to mention other Rust niceties while keeping superficially same convenient semantics.


You know, you don’t NEED to qualify every slightly negative comment about Rust (especially when the comment is made in jest)


I am afraid that I'm just like that. When people jest with me in the real life I often choose to respond as if it was said seriously. I mean, I don't get upset or anything. Just refuse to recognize something as a jest and respond charitably but seriously as if someone was only half-joking. Not sure why I do that. I think I feel that's the best I can do for someone who attempted humor in a way that fell flat on me.

I'm not talking about GP right now. "Thanks, I hate it." actually made me smirk a little bit and his links to assembly were actually super informative. So in this case humor was great but there was also substance which I felt I need to respond to with "what's in it for me".


If you enable optimizations it gets reduced to 200 lines. C++ -O1 reduces it to 30 lines of ASM. Both are awful, just do normal Rust or use something else entirely.

I also believe they would be much closer if the C++ example used shared pointer.


It's because of the libraries. They get compiled once.


It’s because they don’t have the same semantics, you are adding overhead with the Rust version in multiple ways.


I wonder how much difference there would be if I used shared_ptr in C++ instead of the bare one.

EDIT:

As far as I can tell less than 190 lines of asm but I don't know shared_ptr enough to produce exactly the same code.


Yeah, that gets closer for sure. refcell is still going to add a bit of state to the value inside of the allocation, but at least the shared_ptr and Rc/Arc bits are the same semantic, so it'll be closer!


Look at the asm code of rust. You would see that most of the code there is the libraries (and yeah, also because of the semantics, which are powerful).


... the library that implements those different semantics. If you wrote your own Rc and RefCell, that code would still exist, in your crate. Where it lives isn't the root cause.


Whats most impressive for me is that making completely safe, effortlessly mutable and flexible linked list in Rust (or any other similar structure) can be as simple as:

    struct LinkedListNode {
      data: i32,
      next: Pointer<LinkedListNode>
    }
You still need to care about memory leaks through structs linking to each other though because Rc means "reference counted".


How is this any different than using a mutable reference or pointer to the "T"?

  struct Pointer<T> { ptr: *mut T }
  struct Pointer<T> { ptr: &mut T }
This is just adding a ton of overhead and memory usage


You can safely pass Rc<RefCell<_>> around your program and store inside any structures without pretty much any concern for any lifetimes.


came for unsafe, was disappointed




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: