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
I did as suggested trentcl, noting that my pointers would be invalidated on move, I refactored my code so that any
Rowmethods would take the parent table's reference as the first parameter. Then I removed all the<'a>followed the compiler errors until it worked.So the whole problem was allowing the lifetimes to get out of control and using unsafe methods to get parent references. Go figure...