最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Avoiding Self Referential Structs - Stack Overflow

programmeradmin10浏览0评论

I'm writing a program that operates on a Vault object. This Vault object contains a directories field which is a vec of Directory objects. So something like this

struct Vault{
   directories: Vec<Directory>
}

To operate on the vault, I have a VaultManager class. To operate on a directory, I have DirectoryManager class. This is because I am using protobuffs, and it makes life a bit easier.

The DirectoryManager is obtained from a VaultManager::open_dir method, and references a directory from the VaultManager.

struct DirectoryManager<'a>{
  dir: &'a Directory
}

The problem is the following. I want to implement a repl program where I can type commands that open, close and modify different directories. This state should update as I execute multiple commands.

My initial idea was to have a Repl object that keeps state, and commands implementing a ReplCommand trait repeatedly modify the Repl state.

struct Repl<'a> {
    vm: VaultManager,
    running: bool,
    dm: Option<DirectoryManager<'a>>,
}
trait ReplCommand: Sized {
    fn execute(&self, repl: &mut Repl) -> Result<()>;
}

But this api does not work as dm must reference vm, and so once I instantiate a dm I cannot reset it back to None and open a different directory. This is because the lifetimes basically force dm to live as long as the Repl instance itself, if my understanding is correct.

So how can I incrementally update this state without self referencing and avoiding cloning Directories?

I would also like to avoid gaming the problem with some crate. I'm convinced a better design that avoids this must exist.

I'm writing a program that operates on a Vault object. This Vault object contains a directories field which is a vec of Directory objects. So something like this

struct Vault{
   directories: Vec<Directory>
}

To operate on the vault, I have a VaultManager class. To operate on a directory, I have DirectoryManager class. This is because I am using protobuffs, and it makes life a bit easier.

The DirectoryManager is obtained from a VaultManager::open_dir method, and references a directory from the VaultManager.

struct DirectoryManager<'a>{
  dir: &'a Directory
}

The problem is the following. I want to implement a repl program where I can type commands that open, close and modify different directories. This state should update as I execute multiple commands.

My initial idea was to have a Repl object that keeps state, and commands implementing a ReplCommand trait repeatedly modify the Repl state.

struct Repl<'a> {
    vm: VaultManager,
    running: bool,
    dm: Option<DirectoryManager<'a>>,
}
trait ReplCommand: Sized {
    fn execute(&self, repl: &mut Repl) -> Result<()>;
}

But this api does not work as dm must reference vm, and so once I instantiate a dm I cannot reset it back to None and open a different directory. This is because the lifetimes basically force dm to live as long as the Repl instance itself, if my understanding is correct.

So how can I incrementally update this state without self referencing and avoiding cloning Directories?

I would also like to avoid gaming the problem with some crate. I'm convinced a better design that avoids this must exist.

Share Improve this question edited Feb 5 at 3:11 John Kugelman 362k69 gold badges548 silver badges595 bronze badges asked Feb 5 at 2:53 Luca IgnatescuLuca Ignatescu 254 bronze badges 1
  • Is there any reason why you design the api with repl_cmd.exeute(repl) instead of repl.execute(repl_cmd) – 啊鹿Dizzyi Commented Feb 5 at 3:15
Add a comment  | 

1 Answer 1

Reset to default 1

Here's what I would do,

change to vm from taking ownership to reference, that can ensure all of the reference are the same life time. That mean the Vault will need to life outside of Repl.

struct Repl<'a> {
    vm: &'a Vault,
    running: bool,
    dm: Option<DirectoryManager<'a>>,
}

Also, I think that is a better idea to use enum for different command instead of using trait.

since repl command should be pure data, making them trait makes it hard to consume at endpoint.

Example Repl

use std::io::Write;

struct Directory(i32);

struct DirectoryManager<'a> {
    dir: &'a Directory,
}
impl<'a> DirectoryManager<'a> {
    pub fn append(&self, s: String) {
        println!("Appending : {s}")
    }
}

struct Vault {
    directories: Vec<Directory>,
}

struct Repl<'a> {
    vm: &'a Vault,
    running: bool,
    dm: Option<DirectoryManager<'a>>,
}

impl<'a> Repl<'a> {
    pub fn repl(mut self) {
        while self.running {
            self.execute(ReplCmd::read_cmd());
        }
    }
    fn execute(&mut self, cmd: ReplCmd) {
        match cmd {
            ReplCmd::Open(i) => {
                self.dm = None;
                for v in &self.vm.directories {
                    if v.0 == i {
                        self.dm = Some(DirectoryManager { dir: v });
                        break;
                    }
                }
                if self.dm.is_none() {
                    println!("cannot open specified dir")
                }
            }
            ReplCmd::Append(s) => {
                if let Some(dm) = &self.dm {
                    dm.append(s);
                } else {
                    println!("no current dir");
                }
            }
            ReplCmd::Current => {
                if let Some(dm) = &self.dm {
                    println!("current dir : {}", dm.dir.0);
                } else {
                    println!("no current dir");
                }
            }
            ReplCmd::Close => self.dm = None,
            ReplCmd::Quit => self.running = false,
        }
    }
}

enum ReplCmd {
    Open(i32),
    Append(String),
    Close,
    Current,
    Quit,
}

impl ReplCmd {
    pub fn read_cmd() -> Self {
        loop {
            print!(">> ");
            std::io::stdout().flush().unwrap();
            let mut buf = String::new();
            std::io::stdin().read_line(&mut buf).unwrap();
            let words = buf.split_whitespace().collect::<Vec<_>>();
            match words[0] {
                "open" => return ReplCmd::Open(words[1].parse().unwrap()),
                "current" => return ReplCmd::Current,
                "append" => return ReplCmd::Append(words[1].to_string()),
                "close" => return ReplCmd::Close,
                "quit" => return ReplCmd::Quit,
                _ => println!("unkown cmd try again"),
            }
        }
    }
}

fn main() {
    let vault = Vault {
        directories: vec![Directory(1), Directory(2), Directory(3), Directory(4)],
    };

    let repl = Repl {
        running: true,
        vm: &vault,
        dm: None,
    };

    repl.repl();
}
>> open 1
>> append foo
Appending : foo
>> append foo
Appending : foo
>> close
>> open 69
cannot open specified dir
>> open 2
>> close 2
>> open 3
>> current
current dir : 3
>> close
>> quit
发布评论

评论列表(0)

  1. 暂无评论