24 ms·
Bugs that the Rust compiler catches for you
- hu3 4y agolet wordlist_file = File::open("wordlist.txt")?; // do something... // we don't need to close wordlist_file // it will be closed when the variable goes out of scope What happens if there is an error when closing the file that I have written to?
- LegionMammal978 4y agoThe error will be ignored, per the docs for File [0]: > Files are automatically closed when they go out of scope. Errors detected on closing are ignored by the implementation of Drop. Use the method sync_all if these errors must be manually handled. [0] https://doc.rust-lang.org/stable/std/fs/struct.File.html https://doc.rust-lang.org/stable/std/fs/struct.File.html
- josephg 4y agoThat sounds like a weird choice. When do you ever write to a file but not care if the write has failed? Sounds like a source of bugs any time your file system isn’t 100% reliable.
- eptcyka 4y agoIf you close your files without waiting for fsync to return first, do you really care if the data has hit the disk? If fsync didn't fail, but close fails, what can you do then? Calling close() doesn't imply anything about flushing buffers or syncing data to disk or anything like that. It's just a signal to the OS that your process is done with this particular resource.
- CraigJPerry 4y agoYou should probably notify the user though
- eptcyka 4y agoIt depends. Regardless, it seems like Go will close file handles for you too, if you've not done it yet, in the finalizer code.
- ilammy 4y agoAlso, even if close() fails, you can no longer use the file descriptor anyway, since it might be already reused. The only thing you should do with the error from close() is log it to diagnose why it happened and prevent it next time.
- deathanatos 4y agoI agree it's a bit unfortunate. The rub here would be that `Drop` would become fallible, and if it is fallible, then … how does it fail, exactly? (What happens to the error?) There's exceptions, but the downsides to such systems are pretty extensively covered. Nonetheless, the point here is that RAII offers a deterministic close compared to other approaches, at least, even if the write's success isn't covered. You can get that, too, with, wordlist_file.flush()?; or wordlist_file.sync_all()?; depending on desires. (And again, I agree that requiring the programmer to remember code in order to obtain safe behavior is not desirable. But this problem manifests in pretty much any other language, and typically in worse manners.)
- deleted 4y ago[deleted]
- ben0x539 4y agoYeah, it's not an ideal situation. A lot of the time you just read from the file and then you don't really care about the success of close(), though. Maybe that should be a whole set of different types...
- deleted 4y ago[deleted]
- SCHiM 4y agoOff topic: what type of error can occur when closing a file? Is it somehow possible that the kernel denies your request, and forces your handle to stay open?
- TheDong 4y agoQuoting from "man 2 close": https://man7.org/linux/man-pages/man2/close.2.html https://man7.org/linux/man-pages/man2/close.2.html > it is quite possible that errors on a previous write(2) operation are reported only on the final close() ... Failing to check the return value when closing a file may lead to silent loss of data. > the behavior that occurs on Linux ... the file descriptor is guaranteed to be closed. So yeah, it's always closed on linux, but POSIX doesn't guarantee that for EINTR specifically, and there are sometimes meaningful errors.
- lilyball 4y agoFWIW Rust automatically retries the operation on EINTR.
- dllthomas 4y ago> Failing to check the return value when closing a file may lead to silent loss of data. Checking, on the other hand, probably makes it loud loss of data.
- turminal 4y agoWhy would it? If you care enough to check you probably also care enough not to discard the data you attempted to write.
- dllthomas 4y agoI was referring to checking because "you should check" when you don't actually care enough to structure your program around not losing the data. It's easy to to check and emit a diagnostic, it's harder to make sure you have collected the data you've written to a file descriptor and have something sensible to do with it if the close fails.
- deleted 4y ago[deleted]
- verdagon 4y agoAs far as I know, Vale is the only language that can statically ensure we handle that error with its Higher RAII [0], a form of linear typing. Basically, File's drop() returns a Result, and the compiler enforces that we use it. I hear linear types might also be coming to Haskell soon, which is pretty exciting. Such a thing is unfortunately impossible in Rust (though many languages can detect it at run-time). [0] https://verdagon.dev/blog/higher-raii-7drl https://verdagon.dev/blog/higher-raii-7drl
- kitkat_new 4y agowhy is it impossible?
- verdagon 4y agoIn Rust, drop() can't return anything, it must return void.
- zozbot234 4y agoBecause panics in Rust can force-drop data as part of unwinding the stack. If Rust supported panic-free code easily, it could also add linear data which would eliminate the default Drop and replace it with custom, user-managed operations.
- Rusky 4y agoThere's just not a language feature to prevent you from letting an object go out of scope without "using" it. There is `Drop`, which automatically runs when an object goes out of scope, but you can skip that by leaking the object, e.g. with an Rc cycle. There is also `#[must_use]`, which warns when you don't do anything with an object, but you can explicitly silence that with `let _ = the_object` or `let _bla = the_object`, and it doesn't complain if you exit that scope early (either via normal control flow or panic). The reason Rust has `Drop` and `#[must_use]` but not linear types (short of "nobody's bothered yet") is that they aren't really all that pleasant to use and they impose a lot of constraints on the APIs you build with them. You would have to prevent leaks of linear objects (which means, among other things, no reference counting or moving between threads!). You would have to track panics in the type system and/or forbid calling most functions while a linear object is live. More detail here: https://gankra.github.io/blah/linear-rust/ https://gankra.github.io/blah/linear-rust/ The current design means the default way to use a `File` won't handle errors on close, but you can opt into that by making an explicit call yourself. In exchange, you can do a lot of things with `File` objects that you couldn't with a linear `File`. (My suspicion is that Vale either doesn't use linear types for files, doesn't actually enforce linearity at compile time, or makes files painful to use with generic code.)
- deleted 4y ago[deleted]
- TheDong 4y ago> Resources leaks > // defer resp.Body.Close() // DON'T forget this line This doesn't actually leak the file / connection / etc in go for most situations. When the object gets GC'd, the finalizer runs (https://cs.opensource.google/go/go/+/master:src/os/file_unix.go;l=186 https://cs.opensource.google/go/go/+/master:src/os/file_unix...). That closes it.
- eptcyka 4y agoThen what's the point of the Close method in the File interface (https://pkg.go.dev/io/fs@go1.18.1#File https://pkg.go.dev/io/fs@go1.18.1#File) ?
- woodruffw 4y agoI’m not a Go programmer, but I assume it’s there for the same reason every other GC’d language has the ability to close resources manually: sometimes you just want to do it earlier (or more explicitly) than the GC would.
- Andys 4y ago...You might want to close a handle at any time?
- TheDong 4y agoIt lets you check for errors, and have a deterministic time at which the file is closed. Both of these are desirable properties. The finalizer is there to prevent a subset of resource leaks, not to be relied upon.
- ankrgyl 4y agoAt the very least, that does not work for rolling back transactions with the database/sql (https://pkg.go.dev/database/sql https://pkg.go.dev/database/sql) package, although it may work for other cases. We've had numerous production bugs result from this.
- tylerhou 4y agoDoes Go's GC have any SLA about when an unreachable object must be garbage collected? If not, this is risky: 1. Will finalizers run on program crash? 2. Will I run out of resources if the garbage collection doesn't run for sufficiently long time? (E.g. # of open file descriptors.) 3. Does the order of finalization matter? Finalizers in Java were deprecated for the above reasons (and more). https://stackoverflow.com/a/56454348 https://stackoverflow.com/a/56454348
- lordnacho 4y agoAll really good things to mention, but the data races thing is a big deal and gets just a short reference and where is the bit about the borrow checker? The way I see it the things mentioned are nice appetizers and the data race and borrow checker are the main meal. IME the most frustrating problems are not that you forgot to exhaust the switch statement or didn't initialise a new field, it's when you get a segfault that's hard to reproduce and you have barely a hint about what's caused it.
- eptcyka 4y agoI've seen people who state their love for Rust and then fail to explain the difference between passing an argument by value, reference or mutable reference.
- carlmr 4y agoI mean those are two separate things. Depending on how you code, Rust can be similarly high level as Python, make it much easier to design with types than C++ and has great package management with cargo. You can find plenty of reasons to love Rust, without even getting to the technical details.
- deleted 4y ago[deleted]
- deleted 4y ago[deleted]
- Thaxll 4y ago> Rust can be similarly high level as Python Not really, once you start something more complicated than hello world and start to try mutate things for example it get 100x more complicated that Python.
- melissalobos 4y ago
- woodruffw 4y agoThis is pedantic of me, but I think it matters in terms of what Rust actually provides: Rust does not provide any resource leak guarantees. The fact that resources tend to be freed when their owning scope closes is a “fallout” consequence of ownership semantics, but Rust itself does not guarantee that dropping an object necessarily closes or disposes any underlying system resources. You can prove this to yourself by writing a thin wrapper over the nix crate’s POSIX APIs: you can leak a file descriptor by forgetting to add a Drop implementation for it. Similarly, Rust won’t guarantee that all allocated memory is freed. `Box::leak` has well-defined lifetime semantics: it turns a `T` into a `&’static T` by removing its drop handler and leaking the underlying pointer. And this isn’t a problem, because it doesn’t compromise either spatial or temporal memory safety!
- nicoburns 4y agoIt’s true that it’s not a guarantee. But I feel like this is one of those cases where it could be an issue in theory, but pretty much never is in practice.
- Andys 4y agoIf "in practice" is OK, then Go looks pretty good again.
- ben0x539 4y ago"in practice", I forget to close things a lot more often in Go than in Rust.
- Andys 4y agoIn practice, many Go developers use a linter to generate warnings for things you forgot.
- dodobirdlord 4y agoIt’s nice when you don’t have to worry about what linter you should be using because everyone uses the same static analysis to check for this sort of thing and it lives in the compiler.
- LAC-Tech 4y agoNeeds a companion blog, "perfectly safe code the rust compiler will nag you about". And the contortions rust programmers go through to avoid that. Rust is really impressive in a lot of ways. Type classes and pattern are a great fit for systems programming. But they're fixated on the idea that everything possible should be a static analysis error, language ergonomics or usability be damned. I'd much rather these be warnings, because no static analysis on earth is going to stop you from actually needing tests to see if your code works.
- rhn_mk1 4y ago> no static analysis on earth is going to stop you from actually needing tests to see if your code works. I might have been convinced if mathematical proofs were not expressed in code. If a proof can exhaustively cover the problem space, then there's no need for further testing. https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspon...
- LAC-Tech 4y agoFair point, but would you deploy un-tested code to production based on a static proof? AFAIK the issue with this stuff is that the kinds of problems proofs are able to practically cover are very small. Static analysis is an incredibly useful tool, not denying it. I am saying we should avoid worshipping at its altar. Any useful program is incredibly complicated and needs some kind of run time testing.
- yazaddaruvala 4y agoI don't feel like static analysis and unit testing are very different things. Given that my unit tests run roughly at compile time (ideally they would run during compile), it really isn't different than static analysis. Especially unit tests where all the inputs are mocked and expected outputs are provided. When I wrote Ruby/Javascript, I remember needing to write unit tests that verified types of input/output variables. These kinda tests were undifferentiated boilerplate that needed to exist but didn't need to be written by me. Especially since there were many tests I'd forget to add or would intentionally not add because the test file was getting obnoxiously large. Factoring out those boilerplate tests into the compiler (when using Java, Rust, or TypeScript) was very valuable to me, but didn't change the fact that they are basically automatically generated unit tests. The borrow checker in Rust is a similar factoring out of automatically generated unit tests, which I wouldn't have typically written. Continuing to push more undifferentiated unit testing into the compiler/libs/proofs helps make sure I only need to write domain specific unit tests. Why do you feel static analysis and compile time unit testing are different? or do you mean, domain agnostic testing is ok, but really we need domain specific testing?
- Sohcahtoa82 4y agoSide note... Does anyone else feel like RAII is poorly named? It seems to me that the largest benefits of RAII that people talk about aren't about resource acquisition or initialization, but releasing the resource. Taking the phrase "Resource Acquisition Is Initialization" at face value just sounds like it describes object constructors.
- bexsella 4y agoI think even the most hardcore of RAII adherents would say that RAII is a dreadful name for it.
- kjeetgill 4y ago> just sounds like it describes object constructors. I think I've said this verbatim, so you have my vote. I'm curious what other names people might give it. Wikipedia gives: > Other names for this idiom include Constructor Acquires, Destructor Releases (CADRe) and one particular style of use is called Scope-based Resource Management (SBRM).
- Diggsey 4y agoIt comes from C++ where memory being "initialized" to a particular type has a very specific meaning in the language: when a constructor is called on a memory location, it becomes intialized to the type of that constructor, and the reverse when the destructor is called. "Resource Acquisition Is Initialization". ie. You cannot separate the acquisition of the resource from the initialization of the type (and by implication, you cannot separate the release of the resource from the destruction of the type). The language already required that initialization/deinitialization is correctly paired for all programs, so RAII was a way to take that guarantee and use it to manage resources as well. And since for local variables it is the compiler itself that upholds the guarantee, the compiler can also ensure resources are acquired/freed correctly. Without that specific context, the name is poor, but C++ popularized the concept and its name, and once a specific technical name for something exists it's a good idea to continue to use it so as to avoid ambiguity when communicating, even after the originating context is lost.
- kragen 4y ago
- legerdemain 4y agoAlso in Rust, you have combinations of existing and upcoming language features that make certain conceptually simple designs either outright impossible to implement, or only possible to implement a subset of due to certain barely documented compiler quirks, or impossible to determine whether it is actually possible to implement them without sinking hours of time into dead-end prototyping attempts. Like, not "Rust doesn't do this," but "there's a 50% chance that Rust can do this, but it might turn out to be impossible after all, and absolutely no one can tell which is which, because the language is just barely not expressive enough, or the compiler doesn't analyze this particular case, and might not until both the borrow checker and the type system receive complete rewrites." And this, in turn, kind of explains why the Rust standard library and especially the Rust compiler are swimming not just in unsafe code blocks, but code that uses features so unstable and private that you can't even enable them with flags on nightly builds. [0] https://github.com/rust-lang/rust/pull/96709#issuecomment-1118275010 https://github.com/rust-lang/rust/pull/96709#issuecomment-11... [1] https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats https://sabrinajewson.org/blog/the-better-alternative-to-lif...
- eternityforest 4y agoI hope Rust comes out as the winner in the current crop of possible C/C++ replacements. It seems like most of the other make a lot of compromises to keep things simple and open. The bugs are just going to keep happening if we don't do something. And if we don't stop making critical security errors, there could really be a movement to return to analog tech, big enough to set us back 10 years. So many people don't trust computers, and a lot of emerging tech like self driving cars and IoT in the home relies on trust.
- DeathArrow 4y ago>So many people don't trust computers Mostly that's for reasons unrelated to the class of bugs the Rust compiler prevents. I wouldn't trust Google or Facebook more if they were using Rust. :)
- giraffe_lady 4y agoThe reason people don't trust "computers" has nothing to do with bugs in the code lmao. It has to do with the fact that they inescapably track their every move and fuck with their minds to completely inscrutable ends. That shit is basically working as intended, a stricter compiler is not the solution to it.
- nine_k 4y agoWhat are current possible C++ replacements? Rust, as mentioned; it was created as a direct replacement to use at Mozilla, among other things. Zig, which is significantly simpler than C++; good but I'm not sure if it has comparable expressive power (maybe it does, IDK). D, which is around for some time, but hasn't made inroads comparable to Rust. Ada, which is around for even longer, and has enough mindshare in certain industries, but sadly not enough generally. GC-based languages, from Go to Haskell, apparently can't be considered true replacements, even though they are fine for large areas previously dominated by C++.
- girvo 4y agoNim, if ARC/ORC reference counting is acceptable (which considering I'm using it in embedded firmware right now, it honestly should be, but I can understand why for certain cases it can't be). But it has the same small user-base/lack of inroads as some of the others on your list
- avl999 4y agoI root against rust because of its annoying evangelists (not calling this article that but the more general behaviour of the rust community).
- alimov 4y agoI think this is a common reaction to <insert thing> being evangelized. I can’t say it’s a good approach to deciding whether or not something is worthwhile or good or w.e, but maybe it’s better this way because it means that when the thing being evangelized doesn’t pan out, not everyone is caught up in the aftermath. That being said, I don’t have anything against Rust or the community, and if it’s a good tool for <job> then it will succeed with or without me or you. Personally I’m interested in how it’s progressing. Edit: as with many things it’s not right to say that the loudest voices in the room are the community. It could be that this is the behavior you are seeing because somehow it’s managing to get people worked up or engaged in some other way on social media you use, and you find that annoying. If you don’t want to learn the language and engage with the community then you don’t have to, but calling the whole community annoying because people are excited about what they are doing and what they’ve learned and the possibilities they see is… ehhh
- galangalalgol 4y agoI get your point, the community annoys me too, but I was a true believer for a while too and I understand they are just trying to share what made them happy. I root against it sometimes because it feels like its getting so complicated. But mostly I root for it because I have written c++ for a living since before it had an official standard and while the newer standards are better, the compiler refuses to protect me from people who do truly horrific yet subtle things.
- smt88 4y agoHow is it valuable to attack people you don't even see on this thread? A great way to reduce the annoyance of evangelists is to ignore them, not conjure them up in a place you admit they are not even ruining.
- oshi_kuru__ 4y ago1. If the tool is a good fit, why bother with annoying evangelists? 2. I agree, Rust has by far the most (falsely and misleading) advertising about the usage, politics and evangelism going on of all languages around. It annoys me as well. Especially the passive aggressive bullying of Go. 3. Large parts of the Rust community are not acting that way but are friendly and give balanced advice. 4. Focus on the good parts: The language is a good fit for many applications and large parts of the community are great - /r/rust is a good place for example.
- ChiefOBrien 4y ago> It may sound stupid, but you can't have unhandled exceptions if you don't have exceptions... > panic!() exists in Rust, but that's not how recoverable errors are handled. This is the worst argument in the whole article, and this is the worst part of the language. Everyone says it's not like exceptions, but in fact it is much worse. Panic is stringly typed and you can catch_unwind it, just like with try/catch in any other language. And the actual worst part of it, you will never know if a panic can occur in any of the underlying functions until it is too late. Developers be damned if they want to choose different behaviour other than crashing the whole program. Either double down on using the standard error handling everywhere, or put something like "throws panic" in the function signature (ala Java checked exceptions). Many parts of the language has strict checks for everything, why does panic has to be an outlier?
- oxff 4y agopanic! and unwrap are more like assertions about program invariants, but of course this can be abused. at least the linter will yell at you if you have a panic but not documented it.
- justinpombrio 4y agoIt's not like exceptions because it's not used like exceptions. You only use panic if you want to crash the whole program. If you don't want to crash the whole program, you don't use panic. You do not want to crash the whole program if the user data failed to validate, so you do not panic in that case. If a library panics on invalid user data, that's a pretty serious bug. I've been programming in Rust since it came out, and a couple of those years professionally, and I don't think I've ever seen anyone use catch_unwind. Maybe once in a test case? To be concrete, let's talk about an example of a panic. Say you want to access the 3rd element of a vector. There are two cases: 1. You're not sure whether the vector actually has three elements on not. In this case, you call `my_vector.get(2)`, which returns an Option, and you handle the case where it's present and the case where it's not. This is standard error handling. 2. You are sure that the vector has at least three elements. Perhaps you just checked its length for some other reason, or you are careful to maintain this invariant, or you just constructed this vector by pushing 5 elements onto it. In this case, you would typically use `my_vector[2]`, which panics if the vector is too short. For #2, the thing to notice is that this function literally never panics, under any input whatsoever if it is written correctly. Should that fact really clutter up its type signature, either by forcing it to return a Result type or by forcing it to have a "throws panic" marker? EDIT: This is for a function that uses a possibly-panicking operation, `my_vector[2]`. There are also the functions that define a potentially panicking operation, like the vector indexing function itself. You could put a marker in the type signature of those, that would be reasonable. Though it would only be for users; the compiler wouldn't care.
- unnouinceput 4y ago// we don't need to close wordlist_file // it will be closed when the variable goes out of scope AND // mutex is released here as _guard is dropped That's a fancy name for good old garbage collector from Java
- ansible 4y agoNo, it is akin to a try with resources block: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html https://docs.oracle.com/javase/tutorial/essential/exceptions...
- jhgg 4y agoThe key difference however being that finalization of an object is guaranteed when it falls out of scope, and not at the whims of when the garbage collector decides to run. It would be a nonsensical idea to leave up to the garbage collector releasing an acquired lock on its own time. Such a situation could very easily cause a deadlock, as a program may be waiting on a lock that the garbage collector must release, but while the program is waiting on the lock, no additional garbage is being generated that might trigger a garbage collector run, nor is there any guarantee that even if that run does complete, that the objects you wanted finalized would be.
- unnouinceput 4y agoYou do realize you have settings when you want the garbage collector to do its things, yes? As in what you talk about is "lazy" garbage collector, but there are other degrees of when to run. You can set it to be as aggressive immediately after it detects the object is no longer need it, equivalent of "Object.Free" in other languages.
- singularity2001 4y agoNot trying to start a language war, just trying to understand why people prefer Rust over Swift ARC. The best external reasoning I could find is this https://medium.com/@eonil/choosing-swift-vs-rust-237bcb45d97b https://medium.com/@eonil/choosing-swift-vs-rust-237bcb45d97... So here are my own guesses: While Swift looks more polished and much less verbose on the outside, Rust has better 'fundamentals' and community. Swift to Wasm is only an external experiment, not part of core Swift. Pretty much everything outside iOS is only an external experiment, not part of core Swift. If these issues were fixed, would the Swift ARC compiler be preferable to the explicit Rust semantics, or are there other reason ARC is less favorable than the borrow checker?
- nazka 4y agoThere has been effort to be able to use Swift for the backend but it’s not taking off. I like the language for sure. Maybe people prefer to go all in performance by taking Rust?
- shetill 4y agoRust is the low level Haskell of the industry. Everyone talks how great it is, no one uses it for anything that matters or pays
- nazka 4y agoLooks like an obvious troll but it's because you didn't take the time to look at job offers. It starts to really pick up. Even here in Paris we start to have several startups that pick it as their main language. And I am not talking about blockchain startups.
- orangetuba 4y agoAndroid has a lot of Rust now (the bluetooth stack was rewritten in Rust), npm is being rewritten in Rust, Servo is written in Rust, Dropbox' sync engine, Amazon AWS Firecracker, Discord is replacing Go with Rust, Google's new OS Fuchsia has a lot of Rustl. There are also tons of companies looking for hire Rust programmers, e.g. Apple, Intel, Microsoft... there is work to allow for development of drivers in Rust in the Linux kernel. Compare this to Haskell. Where is it being used? What is it replacing?
- shetill 4y agoit will still remain a niche language. Go hasn't and won't replace Java, why would Rust ever replace C++