In a post here some months ago, I mentioned that I couldn't see the cost/benefit proposition for Rust and that part of the reason I felt the costs were too high relative to benefit was the quality of the documentation. I did say that I might revisit Rust as, hopefully, things improve, and I can see that the New Book is growing. So I thought I'd try again and so I resurrected some code I'd written in an earlier attempt that resulted in a pitched battle with the compiler.
Unfortunately, the experience now has been similar to the experience then. I include a snippet of code here that I want to discuss. It is intended to be a report generator that is part of a personal financial mangement suite I have written. The ugly magic numbers in the code below are actually generated by m4. I have my reasons for this that I won't discuss here, since they are not relevant to the Rust issue I want to discuss. Here's the code:
fn build_account_tree(parent:& mut Account, db:&Connection,
mut marketable_asset_value_stmt:Statement,
mut non_marketable_asset_and_liability_value_stmt:Statement,
mut income_and_expenses_value_stmt:Statement,
julian_end_date_time:f64) {
fn get_account_value<'l>(child:&'l Account, marketable_asset_value_stmt:&'l mut Statement,
non_marketable_asset_and_liability_value_stmt:&'l mut Statement,
income_and_expenses_value_stmt:&'l mut Statement,
julian_end_date_time:f64) -> f64 {
let error_message = format!("Account named {}, guid {} had no flag bits indicating account type",
child.name, child.guid);
let stmt = (if (child.flags & 4) != 0 {
(if (child.flags & 1) != 0 {
Result::Ok(marketable_asset_value_stmt)
} else {
Result::Ok(non_marketable_asset_and_liability_value_stmt)
})
} else {
(if (child.flags & 32) != 0 {
Result::Ok(non_marketable_asset_and_liability_value_stmt)
} else {
(if (child.flags & (64 |
128)) != 0 {
Result::Ok(income_and_expenses_value_stmt)
} else {
Result::Err(error_message)
})
})
}).unwrap();
stmt.bind(1,child.guid.as_str());
stmt.bind(2,julian_end_date_time);
newcash_sqlite::one_row(stmt, newcash_sqlite::get_f64)
};
let mut account_children_stmt = db.prepare(constants::ACCOUNT_CHILDREN_SQL).unwrap();
let child_accessor = |stmt:&mut Statement| {
let mut child = Account {
name: stmt.read(0).unwrap(),
guid: stmt.read(1).unwrap(),
value: 0.0,
flags: stmt.read(2).unwrap(),
children: Vec::new()
};
child.value = get_account_value(&mut child, marketable_asset_value_stmt,
non_marketable_asset_and_liability_value_stmt,
income_and_expenses_value_stmt,
julian_end_date_time);
child.flags = child.flags | (parent.flags & (1 |
4 |
16 |
32 |
64 |
128 |
256));
parent.children.push(child);
};
account_children_stmt.bind(1,parent.guid.as_str());
while let State::Row = account_children_stmt.next().unwrap() {
child_accessor(&mut account_children_stmt);
}
account_children_stmt.reset().unwrap();
for child in parent.children.iter() {
build_account_tree(&mut child, db,
&mut marketable_asset_value_stmt,
&mut non_marketable_asset_and_liability_value_stmt,
&mut income_and_expenses_value_stmt, julian_end_date_time);
parent.value += child.value;
}
}
Here is the error that is plaguing me:
error[E0308]: if and else have incompatible types
--> src/main.rs:100:13
|
100 | / (if (child.flags & 32) != 0 {
101 | | Result::Ok(non_marketable_asset_and_liability_value_stmt)
102 | | } else {
103 | | (if (child.flags & (64 |
... |
108 | | })
109 | | })
| |______________^ lifetime mismatch
|
= note: expected type `std::result::Result<&mut sqlite::Statement<'_>, _>`
found type `std::result::Result<&mut sqlite::Statement<'_>, _>`
note: the anonymous lifetime #2 defined on the function body at 87:5...
--> src/main.rs:87:5
|
87 | / fn get_account_value<'l>(child:&'l Account, marketable_asset_value_stmt:&'l mut Statement,
88 | | non_marketable_asset_and_liability_value_stmt:&'l mut Statement,
89 | | income_and_expenses_value_stmt:&'l mut Statement,
90 | | julian_end_date_time:f64) -> f64 {
... |
113 | | newcash_sqlite::one_row(stmt, newcash_sqlite::get_f64)
114 | | };
| |_____^
note: ...does not necessarily outlive the anonymous lifetime #3 defined on the function body at 87:5
--> src/main.rs:87:5
|
87 | / fn get_account_value<'l>(child:&'l Account, marketable_asset_value_stmt:&'l mut Statement,
88 | | non_marketable_asset_and_liability_value_stmt:&'l mut Statement,
89 | | income_and_expenses_value_stmt:&'l mut Statement,
90 | | julian_end_date_time:f64) -> f64 {
... |
113 | | newcash_sqlite::one_row(stmt, newcash_sqlite::get_f64)
114 | | };
| |_____^
There are other errors in this code fragment, some a result of my trying to fix the above error. But let's focus on this. As you can see, get_account_value receives mutable references to three prepared Sqlite statements and the function tries to select from them based on the characteristics of the account it's dealing with. The error is complaining about a lifetime mis-match. If I change all the OK expressions to return the same statement (obviously nonsensical), the error goes away. So apparently the compiler is upset about differences in the lifetimes of the three statements, which are owned by the outer function. But I have provided lifetime annotations. My reading of Chapter 10 of the new book suggests to me that when you assert that multiple references have the same lifetime, if the shortest of their lifetimes meets the compiler's other correctness criteria, then the code will compile. But here, the compiler seems to me to be complaining about an immaterial difference in lifetimes. Either this is a bug, or I am missing something. I don't doubt the latter and if so, an explanation would be appreciated.
/Don Allen