How to make raw pointer to parent struct in rust

69 Views Asked by At

sorry for my poor english I'm a new rust programer from c++ and trying to
make the job done.

Player has many sub module, and then usualy reffence each other. In c++ , we like to save a pointer to Player, so I can access other module or method easilly. I can't imagine how to orginze code under rust's safty check mechenism. Then I try to use raw pointer , but still failed. Here is my codes:


struct Task {
    id: u32,
    status: u32,
}

struct TaskMgr {
    tasks: Vec<Task>,
    pl: *mut Player
}

impl TaskMgr {
    fn get_owner(&mut self) -> &mut Player {
        unsafe {
            &mut *self.pl
        }
    }
    fn submit_task(&mut self, id: u32) {
        unsafe {
            (*self.pl).add_item(1000, 1);
        }
    }
    fn on_add_item(&mut self, id: u32, cnt: u32) {
        println!("task on add item {} {} items len:{}", id, cnt, self.tasks.len());
        self.tasks[0].status =2;
    }
}

struct Item {
    id: u32,
    cnt: u32,
}

struct ItemMgr {
    items: Vec<crate::Item>,
    pl: *mut Player
}

struct Player
{
    task_mgr: TaskMgr,
    item_mgr: ItemMgr,
}

impl Default for Player {
    fn default() -> Self {
        let mut r = Self {
            task_mgr: TaskMgr { tasks: vec![Task { id: 1, status: 0 }], pl: std::ptr::null_mut() },
            item_mgr: ItemMgr { items: vec![Item { id: 1000 , cnt: 1}],pl: std::ptr::null_mut() },
        };
        unsafe {
            r.task_mgr.pl = std::ptr::addr_of_mut!(r);
            r.item_mgr.pl = &mut r;
        }
        r
    }
}

impl Player {
    fn submit_task(&mut self, id: u32) {
        self.task_mgr.submit_task(id);
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        self.task_mgr.on_add_item(id, cnt);
    }

    fn add_item(&mut self, id: u32, cnt: u32) {
        let i = self.item_mgr.items.iter_mut().find(|x| x.id == id);
        if i.is_some() {
            i.unwrap().cnt += 1;
        }
        self.on_add_item(id, cnt);
    }
}

fn main() {
    let mut pl = Player::default();
    pl.submit_task(1);

}

output will be

task on add item 1000 2 items len:486381840784

items len is obviously wrong, why ? how to fix it std::ptr::addr_of_mut!(r) or *mut r both dont work if I move pointer assignment out of default() function, it sounds work.

    let mut pl = Player::default();
    unsafe {
        pl.task_mgr.pl = &mut pl as *mut Player;
        pl.item_mgr.pl = &mut pl as *mut Player;
    }

but this will make code hard to use how to assgin raw pointer when Player create

3

There are 3 best solutions below

1
Yousha Aleayoub On BEST ANSWER

How to make raw pointer to parent struct in Rust?

I think you can obtain a reference to the parent struct and then convert it to a raw pointer using the as keyword. Something like this:

struct ParentStruct {
    value: i32,
}

struct ChildStruct {
    parent: *const ParentStruct,
}

fn main() {
    let parent = ParentStruct { value: 10 };
    let child = ChildStruct { parent: &parent as *const ParentStruct };

    // Accessing parent value through raw pointer in child.
    unsafe {
        let parent_ref = &*child.parent;
        println!("Parent value: {}", parent_ref.value);
    }
}

Docs: https://doc.rust-lang.org/std/keyword.as.html

2
yan huang On

I use Box to move raw pointer assginment where Player created finally

impl Player {
fn new() -> Box<Self> {
    let mut r = Box::new(Self {
        task_mgr: TaskMgr { tasks: vec![Task { id: 1, status: 0 }], pl: std::ptr::null_mut() },
        item_mgr: ItemMgr { items: vec![Item { id: 1000 , cnt: 1}], pl: std::ptr::null_mut() },
    });
    unsafe {
        r.task_mgr.pl = &mut *r;
        r.item_mgr.pl = &mut *r;
    }
    r
}

}

0
Chayim Friedman On

Your code contains Undefined Behavior (use-after-move), as running it under Miri shows. Your "solution" also has UB. New Rust users should not write unsafe code, and even experienced users should avoid it unless they are sure there is no alternative.

If you insist on this design, the proper answer will be to hold Weak<RefCell<Player>>. However, this is a code smell. As can be seen this is also very awkward and prone to panics:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Task {
    id: u32,
    status: u32,
}

struct TaskMgr {
    tasks: Vec<Task>,
    pl: Weak<RefCell<Player>>,
}

impl TaskMgr {
    fn get_owner(&self) -> Rc<RefCell<Player>> {
        self.pl.upgrade().unwrap()
    }
    fn submit_task(&self, id: u32) {
        self.get_owner().borrow_mut().add_item(1000, 1);
    }
    fn on_add_item(&mut self, id: u32, cnt: u32) {
        println!(
            "task on add item {} {} items len:{}",
            id,
            cnt,
            self.tasks.len()
        );
        self.tasks[0].status = 2;
    }
}

struct Item {
    id: u32,
    cnt: u32,
}

struct ItemMgr {
    items: Vec<crate::Item>,
    pl: Weak<RefCell<Player>>,
}

struct Player {
    task_mgr: TaskMgr,
    item_mgr: ItemMgr,
}

impl Player {
    fn new() -> Rc<RefCell<Self>> {
        let mut r = Rc::new_cyclic(|this| {
            RefCell::new(Self {
                task_mgr: TaskMgr {
                    tasks: vec![Task { id: 1, status: 0 }],
                    pl: Weak::clone(this),
                },
                item_mgr: ItemMgr {
                    items: vec![Item { id: 1000, cnt: 1 }],
                    pl: Weak::clone(this),
                },
            })
        });
        r
    }
}

impl Player {
    fn submit_task(&self, id: u32) {
        self.task_mgr.submit_task(id);
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        self.task_mgr.on_add_item(id, cnt);
    }

    fn add_item(&mut self, id: u32, cnt: u32) {
        let i = self.item_mgr.items.iter_mut().find(|x| x.id == id);
        if i.is_some() {
            i.unwrap().cnt += 1;
        }
        self.on_add_item(id, cnt);
    }
}

fn main() {
    let mut pl = Player::new();
    pl.borrow_mut().submit_task(1);
}

Playground.

This can be made to work, but will still be in a bad shape.

The truth is, what you're trying to do is to build a self-referential struct, which Rust is known for not-liking. The best way to approach self-referential structs is to avoid them; they're notoriously hard to get correctly. Re-design your app to not use them. I can't tell how exactly because I don't know your exact interface, but it's often possible, more often than you might believe.

If you cannot, use a library that wraps them nicely is an option too: using ECS libraries in Rust is common for that reason in game development. Actually building self-referential structs yourself should be the last resort, and using unsafe code for that should be absolutely forbidden unless you truly, 100% know what you're doing and you're an experienced Rust developer.