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 |1 Answer
Reset to default 1Here'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
repl_cmd.exeute(repl)
instead ofrepl.execute(repl_cmd)
– 啊鹿Dizzyi Commented Feb 5 at 3:15