5 ms·
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: F
by sergiosgc 12y ago
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.
- krig 12y agoMaybe 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) } }
- sergiosgc 12y agoNote my mention of the too-many-small-functions-with-one-caller(tm) smell. It refers to this solution.
- krig 12y agoI 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.
- sergiosgc 12y agoI 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.
- kybernetikos 12y ago> 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.
- adrusi 12y agoIt 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) }
- jeremyjh 12y agoI think it could be done with a macro that takes a block as its argument.
- Dewie 12y ago> This is a sugar-coated version of errors as return values. By which you mean that errors as return values are bad, and that they are trying to sugar-coat the fact that they are using errors as return values?
- sergiosgc 12y agoNo, 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.
- dbaupp 12y agoUnlike some languages, function return values aren't special, they're just normal values, so you can handle the error locally like match fwrite("aha", f) { Err(e) => println!("Could not write log to database {}", e), Ok(_) => { /* no problem */ } }
- ben0x539 12y agoYeah, but that doesn't extend to match { let f = fopen(); fwrite("aha", f); fclose(f); } { Err(e) => println("either of fopen, fwrite or fclose failed"), _ => () } without something like a try! variant that breaks out of a block instead of returning from a function on error.
- vampirebat 12y agoThis is exactly what the Error / Either monad does.
- ben0x539 12y agoCan't be, it does IO!
- BruceIV 12y agoThere's an RFC for try/catch: https://github.com/glaebhoerl/rfcs/blob/trait-based-exception-handling/active/0000-trait-based-exception-handling.md https://github.com/glaebhoerl/rfcs/blob/trait-based-exceptio...