This is a sugar-coated version of errors as return values. It fixes the most glaring problem, which is code that does not check for errors, common in C:
FILE* f = fopen("somefile", "r");
fwrite("aha", 4, 1, f); // <-- Unchecked use of f here
In Rust (pardon my rust, I'm totally ignorant), it'd be:
let f = fopen("somefile", "r");
fwrite("aha", f)?;
The compiler would know that fopen may return an error, and forbid me from running unchecked code. Nice!
I'd miss, however, the ability to handle all errors occurring from a segment of code in the same place, stuff that exceptions allow for. Pythonish example
try:
db = pgsql.connect(localhost)
stmt = db.prepare('INSERT INTO log VALUES(?,?)')
stmt.execute(('debug', 'Note to self: debug logs are noncritical'))
except Exception,e:
console.write('Could not write log to database %s' % (str(e)))
Sometimes error recovery is the same for all of the segment. I realize one could extract a function for the commonly recovered code, but this may lead to a too-many-small-functions-with-one-caller(tm) smell.
Exceptions aren't the only way to achieve this. If Rust wants to keep return values as the error mechanism, perhaps it could find a way of allowing recovery to happen in the same place for a segment of code.
Maybe I am missing some subtleity here, but isn't the whole point of the described proposed feature that you would be able to replicate the Python code pretty much exactly?
fn read_database() -> Result<Document, DatabaseError> {
let db = pgsql.connect(localhost)?
let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
}
So to have the handling code there as well, wrap it in an outer function (I have no idea if this is anywhere close to actual Rust):
fn read_database() {
fn reader() -> Result<Document, DatabaseError>
let db = pgsql.connect(localhost)?
let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
}
match reader() {
Document(d) => d,
DatabaseError(err) => println!("Could not write log to database {}", err)
}
}
It would be inlined by the compiler, and the standard library could introduce a macro to eliminate the awkward code.
try!({
let db = pgsql.connect(localhost)?
let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
} catch err: DatabaseError {
println!("Could not write log to database {}", err);
})
===>
match (|| {
let db = pgsql.connect(localhost)?
let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
}) {
Document(d) => d,
DatabaseError(err) => println!("Could not write log to database {}", err)
}
Closures aren't "free", you need to structure your code around being unable to return/break/continue across the closure boundary, and there's probably some silly borrow checker errors involved too.
I see, yes. Implementation-wise, I don't think this is a problem, the inner function can easily be inlined by the compiler. But language-wise I agree that it is somewhat ugly. Some kind of sugar over this kind of construction would be nice.
I was not thinking in terms of compilation result. My problem with this solution is readability. Functions get extracted in the name of reusability. When they are called just once, they do not serve that purpose, and just use space in the programmers mental map of the program.
However, I imagine your type of solution could be used in a macro, like jeremyjh suggested in a parallel comment, producing simple code and the expected functionality.
> My problem with this solution is readability. Functions get extracted in the name of reusability
In my opinion one of the biggest benefits of functions is the ability to name pieces of code. I've worked with a number of teams where a single line function with a single caller and a descriptive name is preferred to a line that needs a comment to say what it does.
No, I do not mean that. I mean it is language syntax to aid in using return values for error signaling. There is no intended negative load, although, in retrospect, using "sugar-coated" as "coated in syntactic sugar" may have introduced an unintended negative meaning.
I'd miss, however, the ability to handle all errors occurring from a segment of code in the same place, stuff that exceptions allow for. Pythonish example
Sometimes error recovery is the same for all of the segment. I realize one could extract a function for the commonly recovered code, but this may lead to a too-many-small-functions-with-one-caller(tm) smell.Exceptions aren't the only way to achieve this. If Rust wants to keep return values as the error mechanism, perhaps it could find a way of allowing recovery to happen in the same place for a segment of code.