6 ms·
This line caught my eye. my_file.open(); // Error: this may fail. 1) If this can fail, then it should be a compile error to not test the result code. 2) IMH
by jbritton 7y ago
This line caught my eye.
my_file.open(); // Error: this may fail.
1) If this can fail, then it should be a compile error to not test the result code.
2) IMHO it would be nice if there was something like Python's with statement to correctly close a file.
with open(filename, 'r') as f:
f.read()
# f.close() invoked automatically here
This prevents trying to close a file that is not opened.
The idea of encoding a state machine into the types seems interesting.
- strictfp 7y ago1) A typesafe variant of this example, caught by the compiler, is what he implements further down 2) There is. Rust has the Drop trait, which guarantees destruction when going out of scope. Even better than `with` statements since the API doesn't require anything special from the user.
- andolanra 7y agoYou should probably have read the rest of the blog post, because in Rust it is a compiler error to not test the result, and the post specifically calls this out. Specifically, look for this section: let mut my_file = MyFile::open(path)?; // Note the `?` above. It's a simple operator that asks // the compiler to check whether the operation succeeded. // The *only* way to obtain a `MyFile` object is to // have a successful `MyFile::open`. // At this line, `my_file` is a `MyFile`, which means // that we may use it.
- Thaxll 7y agoIs it though? Does the compiler not compile when you don't check / use the error result?
- kccqzy 7y agoIf you don't use the result, how would you be able to get the handle that represents the open file?
- inferiorhuman 7y agoYou wouldn't. In this case a more likely example would be writing to the file. You don't have to check the return value (in C or Rust) to do something useful, but you should. In Rust you'll get a warning (or error depending on how your workspace is setup). In C, you won't get anything. It's also worth noting that the Result enum can be and is used for things beyond file I/O.
- andolanra 7y agoIf you were to open a file that didn't exist and then not use the result at all, then you get a warning (which can be made an error, if you want): File::open("foo"); // warning: unused `Result` that must be used But that's also not necessarily an invalid program, it's just not a very useful one. Failing to open a file doesn't interrupt the program in any way, so File::open here will return either a file handle or an error, neither of which get used. However, if you open a file, don't check for an error, but still try to use the file, then your program won't compile: File::open("foo").read(&mut buffer); // error: method `read` not found in `Result<File, Error>` You need to be explicit about what to do in case the file didn't open, and Rust won't let you use your file handle unless you explicitly indicate what to do in that error case!
- james-mcelwain 7y agoIn actual Rust APIs, opening a file returns a result, which needs to be handled or unwrapped in order to operate on the file. All resources in Rust are implicitly like your Python with example -- when they go out of scope and destructors run, the file will be closed.
- MaulingMonkey 7y agoRE 1: Several languages (including Rust!) have annotations to force you to test the result code. However, they generally leave my_file in scope even in the error path, which makes bugs easier to write, and which means the File type is forced to handle all the complexity of handling the case where the file wasn't successfully loaded for it's entire API surface. RE 2: Rust takes the C++ approach of allowing types to define their own cleanup which gets auto-invoked when the object goes out of scope, instead of forcing the calling code to worry about it. Specifically, Rust types can implement the auto-invoked "Drop" trait, basically equivalent to C++'s destructors. You only need to call close if you want to close a file early (e.g. before the end of the scope), in Rust, which is fairly rare. I vastly prefer this approach, but it admittedly doesn't play nicely with the... less deterministic lifetimes of objects in a garbage collected system, so I can understand why Python, C#, etc. have more explicit scope syntax for cleanup.
- deleted 7y ago[deleted]
- ChrisSD 7y agoIt's interesting to take note of the actual Rust `File` object: https://doc.rust-lang.org/std/fs/struct.File.html https://doc.rust-lang.org/std/fs/struct.File.html `open` is a static function on the `File` object and an instance of `File` does not have a `close` method at all. So, as in this article, an instance of `File` can only be created if `open` succeeded and the file will be closed when dropped (either implicitly or explicitly). For example: fn main() -> std::io::Result<()> { // a file object is only created if File::create is successful // otherwise main returns an error let mut file = File::create("foo.txt")?; file.write_all(b"Hello, world!")?; Ok(()) } // file closed here, no close method needed. This contrasts with many other languages where a class instance can be in an invalid state. This is something the typestate pattern helps to avoid.
- ridiculous_fish 7y agoWhy isn't there a close method? It seems strange to omit this, given that close can return an error. edit: also closing stdin/stdout is common.
- empath75 7y agoIt automatically closes once it’s out of scope.
- winstonewert 7y agoYou can see here some discussion of that question: https://github.com/rust-lang/rust/issues/32255 https://github.com/rust-lang/rust/issues/32255 https://github.com/rust-lang/rust/issues/59567 https://github.com/rust-lang/rust/issues/59567
- ChrisSD 7y agoThere's a sync_all method that will make sure all data and metadata is written or else return an error. This may be used at any time, not just when closing the file, and it is expensive so it does make sense to have a separate method for that case.
- paulddraper 7y ago> it would be nice if there was something like Python's with statement to correctly close a file. In C++ or Rust, non-memory resource management is arguably even easier than Python. RAII: Resource Acquisition Is Initialization. (Apologies for the C++; can do the same thing in Rust.) ManagedFile myfile("path.txt"); myfile.read(); `myfile` gets released whenever the scope finishes. That said, the C++ and Rust stdlibs don't look like that because opening and closing files is not exception-free and unlike Python, C++ and Rust don't have or don't prefer non-explicit error handling.
- gameswithgo 7y agoWhen you get a result type back in Rust you have to check it to use the value it contains. The worst you can do is call unwrap(), which says "Im assuming this will not fail, panic if I am wrong". But you can't forget to check.
- pornel 7y agoIn Rust this is achieved using `if let` for anything that uses `Result` and `Drop` (it's kinda neat how a few orthogonal features fit nicely together like that): if let Ok(f) = File::open(filename) { f.read(); // drop(f) automatic here } But Rust's move semantics give you extra flexibility, because you don't have to close it if you don't want to. let keep = None; if let Ok(f) = File::open(filename) { f.read(); if random() { keep = Some(f); } } // the file *may* be usable beyond the first scope, // and it's still dropped correctly. Values aren't dropped simply at the end of their initial scope (as in `with` or on-stack RAII), but after their last use, and language semantics allow the compiler to track globally where the last use is.
- steveklabnik 7y ago(drop types are still dropped at the end of lexical scope, even post NLL.)
- pornel 7y agoI've meant that if a value is moved, it won't be dropped in the scope it came from, but the one it's been moved to.