I've been working on creating a Table
struct that can resolve to a Val
trait object along with other data types (Number
implemented as an example) and can be seen in the Rust Playground here. I haven't been able to get past this borrow/lifetime issue but the idea is that Number
and Table
are both Val
objects. However, any attempt to write the object to a String
buffer causes a failure in compilation.
What am I doing wrong here? And what is the rationale for the borrow checker to operate in this way?
use std::fmt;
use std::fmt::Write;
struct Number(f64);
struct Table<'a> {
rows: Vec<Row<'a>>
// Will have column vector as a member
}
impl <'a>Table<'a> {
fn new(rows: Vec<Vec<Box<dyn Val>>>) -> Table {
let mut table = Table {
rows: rows.into_iter().map(|r| {
Row {
parent: std::ptr::null_mut(),
cells: r
}
}).collect()
};
let parent = &mut table as *mut Table;
table.rows = table.rows.into_iter().map(|mut r| {
r.parent = parent;
r
}).collect();
table
}
}
struct Row<'a> {
parent: *mut Table<'a>,
cells: Vec<Box<dyn Val<'a>>>
}
impl <'a>Row<'a> {
fn to_str(&'a self, buf: &mut String) -> fmt::Result {
let mut cell_iter = self.cells.iter().enumerate().peekable();
let _parent = unsafe { self.parent.as_ref() }; // Real implementation will need parent ref
while let Some((idx,_c)) = cell_iter.next() { // Real implementation will cycle through columns in parent struct
match self.cells.get(idx) {
Some(v) => v.to_str(buf),
None => Ok(())
}?;
if let Some(_) = cell_iter.peek() {
write!(buf, ",")?;
}
}
Ok(())
}
}
pub trait Val<'a> {
fn to_str(&'a self, buf: &mut String) -> fmt::Result;
}
pub trait ObjWriter<'a> {
fn to_str(&'a self, buf: &'a mut String) -> fmt::Result;
}
impl <'a>ObjWriter<'a> for dyn Val<'a> {
fn to_str(&'a self, buf: &mut String) -> fmt::Result { self.to_str(buf) }
}
impl <'a>Val<'a> for Table<'a> {
fn to_str(&'a self, buf: &mut String) -> fmt::Result {
write!(buf, "(START TABLE:")?;
let mut row_iter = self.rows.iter().peekable();
while let Some(r) = row_iter.next() {
r.to_str(buf)?;
write!(buf, "\n")?;
}
write!(buf, "END TABLE)")
}
}
impl Number {
fn to_str(&self, buf: &mut String) -> fmt::Result {
write!(buf,"{}",self.0)
}
}
impl <'a>Val<'a> for Number {
fn to_str(&self, buf: &mut String) -> fmt::Result {
self.to_str(buf)
}
}
fn main() {
let table = Table::new(vec![
vec![Box::new(Number(0.5)),Box::new(Table::new(Vec::new()))],
vec![Box::new(Table::new(Vec::new())),Box::new(Number(0.5))],
]);
let mut buf = String::new();
table.to_str(&mut buf);
println!("{}",buf)
}
error[E0597]: `table` does not live long enough
--> src/main.rs:98:5
|
92 | let table = Table::new(vec![
| ____________________________-
93 | | vec![Box::new(Number(0.5)),Box::new(Table::new(Vec::new()))],
94 | | vec![Box::new(Table::new(Vec::new())),Box::new(Number(0.5))],
95 | | ]);
| |_____- cast requires that `table` is borrowed for `'static`
...
98 | table.to_str(&mut buf);
| ^^^^^ borrowed value does not live long enough
99 | println!("{}",buf)
100 | }
| - `table` dropped here while still borrowed
'static
in the error message comes fromBox<dyn Foo>
being a shorthand forBox<dyn Foo + 'static>
. I tried adding+ 'a
to your dyn boxes, but I got a different error related to lifetimes. Not sure this helps, but maybe it nudges you in the right direction. – user4815162342new
returns. – trentcl'a
s here, it's very confusing. You almost never want&'a self
in a function parameter, so those would be the first to get rid of. The problem is it's tricky to remove lifetimes in code that usesunsafe
especially when the code is already known to be unsound, as I already pointed out this code is. How are you going to deal with the problem that as soon astable
moves, all the pointers to it are immediately invalidated? Once you have that figured out, we can talk about how to prove it correct with lifetimes. – trentcl