40 ms·
Implications of Rewriting a Browser Component in Rust
- mrath 8y agoI primarily use Java for my job. Security and memory related features of Rust are not an advantage compared to Java. But I like rust because it feels modern and produces efficient standalone binaries. Most of my hobby projects are in Rust now. But I would not rewrite any of my work projects in Rust even though they require ultimate performance. That would be a maintenance burden. It is great to see people rewriting in Rust where it makes sense.
- orf 8y agoNullpointer exceptions are a huge thing in Java.
- Eridrus 8y agoI haven't written any Rust in a few years, but last I knew, it was very possible for a Rust program to crash. The guarantee is that it won't corrupt memory while doing so.
- gpm 8y agoThat's true, but 'null pointer exceptions' (which I suppose means expecting a Option<&T> to contain a &T when it is instead empty) are much rarer since nullability is made explicit. Rust goals in life towards bugs are basically - Isolate memory bugs to unsafe code which you rarely write. - Make as many classes as bugs reasonably possible rarer by encoding as much as is reasonable in the type system and encouraging the programmer to think about all possible cases. The first gets all the attention because it is the one you can make guarantees about, but the second is really just as important.
- vaylian 8y agoThere are different reasons why a program can crash. Nullpointer exceptions are one of them. Out-of-memory situations are another. Rust protects you against the former, but not the latter. But at least in my experience I only encountered crashes when I disabled safety checks by calling .unwrap().
- jononor 8y agounwrap() does not disable safety checks, it just means you ignore/disregard error handling. It will panic and abort the program, but not cause a safety issue. /pedantic
- amalcon 8y agoThis is actually an interesting distinction. The way that Rust defines "safety" is a little jargon-ish. It's probably more useful than the (highly impractical) colloquial meaning, but still different. In a rust context, "safety" means that the program is protected from a very specific set of things. One might expect that set to contain things like crashing (panic! and friends) and leaking memory (forget). It does not, and is not intended to.
- dullgiulio 8y agoBut the parent's point remains: a Java NPE and a panic because of unwrap are really equivalent. Not really unsafe in the C sense, but equally bad as unhandled crashes.
- Yoric 8y agoAssuming that nobody catches the NPE by accident or error (according to my experience in Java, it's a pretty big assumption), yes, they pretty much are. The big difference is that, in Java, you typically get your NPE because you didn't know/didn't check that your pointer could be `null` – in other words, the default behavior of the language is to NPE. In Rust, the default behavior of the language is to inform the developer that they need to check. They may decide to explicitly assert that the pointer is not null, causing a crash if it is, but that's a conscious choice. For instance, in my Rust code, pretty much every occurrence of `unwrap()` or `expect()` contains a comment explaining which invariant guarantees that the call will succeed. I don't think I have ever seen any comments associated to a member access in Java.
- jjnoakes 8y agoIt is possible, but the causes and frequencies are quite different. In Rust runtime panics are certainly possible (array out of bounds, out of memory, etc), but the ones analogous to Java's NullPointerException have to be explicit opted into (unwrapping optionals) and do not happen implicitly. Rust lets you handle optionals in nicer ways which lets you be sure you covered the "null" cases at compile time with no runtime panics possible, if you like.
- mrath 8y agoI think that is somewhat related to the article. There a class of bugs not possible in Rust, they are also not possible in my GCed langs. unwrapping an `Option` is similar to NPE. but that code does not feel idiomatic Rust. IMO there is higher chance of making NPE mistake in Java than unwrapping None in Rust for similar logic.
- latencyloser 8y agoI write Java daily, I haven't seen one in a production environment in... at least recent memory. Most common NPEs I've seen can be resolved by doing two things: 1) for returning a single, nullable value, wrap it in Optional. 2) Never return null collections, always default to an empty collection instead. More nuanced ones can often be caught by using all args constructors and requiring the constructor values with Objects.requiresNonNull() or similar. Using spring? Don't use field or setter injection, always constructor so the above applies as well. Making the state of your objects largely immutable means their state is more consistent and what is null and when becomes a lot less surprising. Lastly, write good tests. If you're exhausting the behavior of your system with tests, these things are much less likely to surprise you later. NPEs are definitely a problem with Java, but they're a very avoidable one as well. Edit: I don't understand the downvotes. The parent said they're a huge deal and I'm disagreeing because I think they're relatively simple to avoid?
- woah 8y agoRust basically just enforces the things you mention
- pjmlp 8y agoWith the cost of catching up to 25 years of tooling and libraries.
- bluejekyll 8y agoWe’re getting there. I dislike this argument, I’m sure the same was made in favor of C when Java came out.
- pjmlp 8y agoI agree with you, it is just a call to reality to those "lets rewrite everything in language X", without consideration that there is more to it than just grammar and semantics. Also there are plenty of domains where Java or C++ aren't even taken into consideration, in spite of decades of effort. Just to cite Microsoft's recent security recommendation out of Blue Hat conference, regarding their own software, "use a mix of C#, Rust and constrained C++".
- FooBadDev 8y agoWouldn't using Koltin or Scala, which benefit from the engineering effort of JVM and arguably its ecosystem solve this problem ? (Since NULL is not idiomatic in either language)
- 14113 8y agoIt's not idiomatic, but it's still possible. I've got NullPointerExceptions while writing fairly complex, modern Scala, so it's about as possible as with Java in my experience.
- on_and_off 8y agoYes and no : I have been using kotlin for a while for Android. Currently our codebase is 75% kotlin. Issues arose at the interface between java and kotlin. Unless there are @Nullable @NonNull annotations (and they need to be truthful), the kotlin compiler cannot know the nullability of something coming from a java method. It can be pretty pernicious : if you use some java written libraries like Moshi (json parsing), it can also lead to crashes : IIRC if you declare a moshi generated property to be non null but it is absent from the json, it will generate an object with null, creating a crash. Still, null is now an anecdotal issue in Kotlin. The Android framework team is working on annotating all their APIs with the corresponding nullability annotations and more and more JVM libs are also working on handling it gracefully. It was never a huge issue to begin with even in java, just pretty cumbersome to have to add annotations everywhere and have to add some policies like 'no null collections, only empty' in a codebase to have sane handling.
- bluejekyll 8y agoFor me, also primarily a Java person before Rust, it’s datarace safety that captured me. Java’s executors improve the world, but I can’t count the number of concurrency bugs that I had in Java that are not possible in safe Rust. This isn’t just my code, btw, but large teams where it’s hard to disseminate good practices when building threadsafe code. If it had been in Rust, those issues wouldn’t have happened. Other things that I appreciate about Rust over Java is the error handling combined with RAII, doing away with nasty bugs around try’s lacking proper finally statements for closing file handles, etc. Java has its warts, not everything is just about memory safety.
- mrath 8y agoabout RAII there is quite a bit of support in recent Java versions not quite as good as Rust but there is support. Data race is one other thing, I do have data race issues but that is very rare. Some of the static analysis tools even catch these anti patterns.
- agumonkey 8y agoCurious about memory usage too
- bluejekyll 8y agoI think the best way to look at it is that memory usage becomes predictable and GC pause free. An application of similar scale and implementation between Java and Rust won't necessarily use less memory when in Rust.
- agumonkey 8y agoNaively I thought Java data model was inherently more demanding than Rust but .. I never read about Rust memory layouts.
- ianlevesque 8y agoI use Java constantly in my job and recently tried rewriting a math & memory heavy component in rust to see what performance gains there might be. Surprisingly (to me) the naive rust version was ~15% slower than Java. There’s probably room for more rust optimization but it was interesting that “efficient standalone binaries” doesn’t automatically mean faster too when competing with HotSpot.
- pjmlp 8y agoGoogle had a fast math library for Android, which got outperformed by ART JIT compiler and was eventually deprecated. https://developer.android.com/reference/android/util/FloatMath.html https://developer.android.com/reference/android/util/FloatMa...
- carlmr 8y agoI find where rust usually shines the most is if you do text processing. String allocations take time. In rust you can often avoid them and use things like cow to only allocate when you change something. That way often my text processing heavy scripts go twice the speed of a C++ version, and they're easier to write with Cargo, too. Compared to highly optimized Java and C# I could often get a quite naive rust implementation to be 10x faster. Naive rust means I didn't spend much time optimizing but I do use appropriate algorithms and to avoid unnecessary allocations.
- QuercusMax 8y agoMy experience working on writing image processing code in Java is that rewriting the slowest bits in C++ only gave about a 10% performance boost, and this was over a decade ago. Moving stuff to the GPU was vastly more effective than doing faster CPU work.
- alex_duf 8y agoWhen comparing speed on the JVM with speed with native languages, the only positive side you get from native binaries is cold start nowadays. For a webapp this doesn't matter, but as we're moving towards more cloud functions it start to make a lot of sense. That needs to be ponderated by the fact any real life application will have to access the network at bootstrap to load configuration and therefore your bottleneck will most likely be I/O.
- rkangel 8y agoIt's nice to see a balanced, real world, case study including 'these things are fixed by Rust', 'these are problems that don't occur in idiomatic Rust', and 'these are problems that Rust can't help you with'. I'm a big fan of Rust, but the one sided 'Rust makes all the problems go away' articles don't provide any value.
- tspiteri 8y agoIt also highlights an example of a security bug introduced during rewriting; highlighting that rewriting any significantly large piece of software is bound to introduce bugs.
- dmix 8y agoNot just introducing new bugs but reintroducing old bugs which were publicly documented and/or previously exploited. Which you could argue are worse as it’s a lower barrier for detection by attackers, but also on the otherhand by the team/community. Also of note was that there was already an automated test for one of the high priority bugs that got reintroduced but the that particular tests was turned off.
- eridius 8y agoWhat confuses me about this is the tests were turned off because they were taking too long. But wouldn't the appropriate behavior there be "run a subset of the tests normally, but run the full test suite occasionally" rather than just disabling the tests completely?
- dmix 8y agoOr turn off some during development but run the whole suite before release? I have a feeling the a bunch of the tests in that particular category needed to be updated, so it wasn't simply just too long.
- indolering 8y ago
- gubbrora 8y ago> could have been caught by a run time bounds check And here I thought rust was all about zero cost abstractions.
- blub 8y agoIt's not, that's C++. Rust requires a lot of runtime checks, but that's the price one has to pay for memory safety.
- rat9988 8y agoRust is about zero cost abstraction. Bound checks can be disabled.
- blub 8y agoAnd then it's no longer memory safe, which makes the whole exercise pointless...
- phkahler 8y agoEven in that case it's not pointless. You can do a lot of testing and fuzzing with checks enabled to get some confidence that you don't have bugs. Then disable the checks for performance. That's just one option. I think it's nice to have options even if I choose not to use them. Even better is to use iterators and other abstractions that don't require bounds checking at all. Rust has lots of good tools to build efficient code.
- littlestymaar 8y agoIf your use iterators instead of indexes, you have no bound-checks because they are optimized away. I've been working full time with rust for a year and I'd say I need indexes less than 10 of the time, most of which are for fixed-size array with constant indexes, for whom the bound checks are also optimized away. So I'd say they aren't really a problem in practice 95% of the time. If you really need to go unsafe for the last 5%, well you're still 95% safer than C++ :)
- rini17 8y agoDoes Rust allow for taint analysis too like Perl has for long time? If not I'd say it's missed opportunity. (It marks all untrusted input as tainted and programmer must explicitly parse the data or mark them untainted to pass them further.)
- steveklabnik 8y agoThere's not a language feature to do so, but you can do it through the type system if you wish.
- palotasb 8y agoIn Rust, or any statically typed language such as C++ or Java, the idiomatic way to handle untrusted input is to treat it as a "bag of bytes" before you access it. Then either parse it into a strongly typed object or bail out of parsing. The strongly typed object is safe to use. Bailing out (throwing an exception or returning an error type) does not allow the program to continue assuming that the (malformed) input was correct.
- chopin 8y agoMore to the point, you should put untrusted input into a different type from trusted input. As much as I admire the design of the servlet API I think the biggest mistake is that everything is transmitted as Strings. The input characters should have had a different type than the output characters.
- SamReidHughes 8y agoThat's a feature that might work well with some plausible ways of guaranteeing memory safety in a type system because object reachability is a form of taintedness.
- jupp0r 8y agoOne of the major hurdles in rewriting parts of C++ projects in Rust is that the interop surface between both languages is C. The necessary interface layer has created more bugs and work than the conversion saved. I'd really like to see more high-level interoperability between the two languages in the future, although C++ is a pretty fast-moving target at this point, with all the changes in C++20.
- steveklabnik 8y ago> The necessary interface layer has created more bugs and work than the conversion saved. Is this from a project you did? This is weakness, for sure, but I'd be interested in hearing more about why it failed for you! We have some people working on this.
- WhitneyLand 8y agoThat's a proportional problem at least right? As months pass the more of your libraries converted to Rust the lesser the problem. The potential party spoiler being third party code that's not practical to replace. In some cases even decades won't change it's nature, as a few examples have shown.
- cma 8y agoAt some mid point you would have maximum C glue code in place, and then things get better after that.
- jcranmer 8y agoHonestly, I wish a few different major languages would get together and start developing system ABIs that move beyond C as the interchange language.
- wilsonthewhale 8y agoBut what would you put in such an ABI beyond what's in the C ABI? Beyond basic data types, struct defintions, and function definitions, languages begin to wildly diverge almost immediately.
- atoav 8y agoThis is in tune with my own experience using Rust in production: it can stop you from doing certain classes of mistakes, but it won't stop you from doing stupid things. But the idea that I don't have to think about certain classes of problems allows me to give these stupid things more focus, which is surprisingly refreshing. The predictable nature of Rust was so refreshing for me that I ended up using it even for smaller reusable scripts where I would happily have used Python before but soon got annoyed with obvious errors that would only show up once you run a program. If you e.g. have a `print foo` in some obscure branch that rarely happens, that print will ruin your day if you use Python 3. If python would be a little like Rust you would get on save (or at least on compile) a hint or error, that the print should look like this: `print(foo)` for Python 3. You can be incredibly careful and rust will still catch things now and then, that would have gone unnoticed into production unless you have immense test coverage. I like Rust for the experience I had with it. It definitly changed how I approach certain problems in a very good and productive way, even when I don't use it.
- FreeFull 8y agoPython will actually give you an error for `print foo` as soon as it parses the file. But there definitely are other scenarios where you'll only get the error in the middle of execution (such as passing the wrong type of thing as an argument to a function)
- wyldfire 8y agoAgreed but Python lazy-loads & parses files as packages are imported. By convention these are all at file scope and at the top of the files, so it's often confined to an initialization phase. But...sometimes developers use clever fallback behavior by catching ImportError. So the scenario described is possible to escape simple tests, I suppose.
- marmaduke 8y agoDoesn’t the print raise a SyntaxError not ImportError?
- ilovecaching 8y agoRust is often sold feature by feature; the borrow check offers proof like safety over fuzzing, cargo provides real versioned package management over makefiles or git commits... I choose Rust because taken as a whole, Rust changed the way I approached laying out my memory and how I composed my code. I think this more than anything leads to less issues than the equivalent C++. The article points out that a Rust vs C++ solution to any given problem are going to be completely different. My only desire for Rust is to see compile times speed up and the C++ interop to improve.
- mrath 8y agoYes compilation times are a big pain point. I heard that there is work being done in this area.
- deleted 8y ago[deleted]
- rujuladanh 8y agoThe article is arguing that Rust somehow has better capabilities than C++ to fight memory-related bugs, but the example vulnerability given is not something Rust can solve nor is more powerful than C++ in its “bug catching” capabilities regarding this kind of bug. Concretely, the article claims that in Rust the vulnerability doesn’t become a bigger problem because it simply crashes at run-time due to built-in bounds checking. True, but that is alao the case as well with C++ if you were using the equivalent Vec type with mandatory bounds checking - which many projects do (and, critically, enforce). Personally, I like what Rust brought to the compiler/language world. However, some people is definitely overstating the case. Most non-trivial memory-safety errors and vulnerabilities are related to runtime problems like the example shown. In these, no language can help in the general case - we are not solving the Halting Problem. Therefore, saying Rust is immune to memory-related problems is not true. It is true, however, that those bugs will not trigger anything worse than a crash if there is no unsafe blocks. The same way that many other common languages out there do (Java, C# and many others). The same way, I have seen people (and even the linked blog) to claim Rust is free of race conditions or thread-safety issues (even if it introduced great ideas to write correct code). Giving a false sense of security is the worst thing we can do.
- rujuladanh 8y ago(Continued...) It is not realistic either to ask everyone and every company to rewrite all their C/C++ code in Rust. Even if it were financially doable and a rewrite were to happen, in many cases it would simply be best to move to a language like C# anyway, not Rust; for productivity reasons. Where performance allows, of course. In my opinion, the realistic and pragmatic solution is, instead, to strive to make all languages (in particular C and C++) embrace security-first approaches/types/mechanisms like Rust does. The compiler tecnology is already written - now retrofit as much as possible into C++ (even to the point of introducing a “safe” scope if needed) and allow companies to embrace it at minimal cost and progressively.
- scoutt 8y agoA system crash is a bug. Period. In many cases it could lead to Denial of Service. An insulin pump can stop working. I remember when C# came out almost 20 years ago. People said "I can forget about managing memory so I can focus on the logic". Programs kept crashing, memory problems were still there. The article goes with "...remove the burden of memory safety from our shoulders, allowing us to focus on logical correctness and soundness instead...". More or less the same, and admitting that said problems won't go away. But here we are, it's 2019 and we're still using C/C++ as if nothing happened.
- herogreen 8y agoCan you build in some kind of "unsafe release" mode, so that every array bound check that were asked in the code are skipped ? If not, would it be an interesting feature ?
- steveklabnik 8y agoNo. Such a thing could only remove some kinds of checks; for example, if you see that code sample later in the thread with a manual check, it wouldn't know that's what you're doing. In general, we don't want to make it easy to turn checks off. They get removed if the compiler can prove they're not needed; if they're there, they're almost always for good reason.
- sanxiyn 8y agoThis is trivial to implement, but it will never be accepted by Rust upstream. There will be a fork if someone really wants this.
- dagmx 8y agoYou could do something with rusts feature system and macros. In essence you'd have a macro that would run a different line of code if your feature is enabled versus disabled, so you could use the unbounded lookup on the array. That said, this would be a user implementation and wouldn't be likely to be provided by the standard Library
- guscost 8y agoRecently some colleagues started using the type annotations in the latest python3. Really excited for this feature! It’s going to make a lot of our production systems safer to work with. And of course Rust is a great technology, etc.
- tonetheman 8y agoMeh. The whole thing seems weird to me. We totally tried to write this twice then we switched to language x and everything is great. Feels like something a language zealot would say. I would scoff if someone at my company rewrote a core section in a different language. It is their language so maybe they just told them to do it that way ha.