[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
|
|
Subscribe / Log in / New account

Toward a more approachable Rust

February 22, 2017

This article was contributed by Eddie Kovsky

The Rust programming language is designed with the principle that developers should never have to choose between performance and safety. While performance has long been the domain of the C programming language, C can often be the source of security bugs. Rust might one day be a viable replacement, but it is still maturing. This year, the project's leaders are changing their focus to make sure the Rust ecosystem can continue to grow.

C has long been the workhorse of low-level development. It has held up remarkably well over the years. As its creator once quipped: "C is quirky, flawed, and an enormous success." C grants programmers the freedom to write high-performance code that is virtually unmatched by any other language. That freedom also comes with its own pitfalls. There is even a book devoted to the subject. That same freedom creates the opportunity to introduce bugs that would not be possible in a higher-level language.

Rust has a strict type system that is designed to eliminate most of the memory-management errors that are a common source of bugs in low-level languages. (Rust's feature list even includes "guaranteed memory safety".) Rust introduces some new ideas not seen in other mainstream languages that make this possible. But these unique features, combined with Rust's rich syntax, may also be a barrier to the widespread adoption of the language.

Rust aims to match the performance characteristics of C while eliminating the safety risks. This is not enough, however, to guarantee its success. The Rust project leaders have decided that the biggest challenge facing the language is improving the on-boarding process for new programmers. While Rust is still young, the first stable release was in May 2015, most of the design and technical features are set in place. In 2017, there will not be any focus on new changes to the language or the standard library:

[...] our primary challenge isn't making Rust "better" in the abstract; it's making people productive with Rust. The need is most pronounced in the early stages of Rust learning, where we risk losing a large pool of interested people if we can't get them over the hump. Evidence from the survey and elsewhere suggests that once people do get over the initial learning curve, they tend to stick around.

When the Rust project conducted its 2016 survey it asked users what stood in the way of being able to use Rust. The top responses received were:

  • 1 in 4: learning curve
  • 1 in 7: lack of libraries
  • 1 in 9: general "maturity" concerns

Some of the top priorities this year include improving documentation, for example a new version of the Rust book is under development, improving error messages generated by the compiler, and refining editor integration and other tooling. The full details are available in the 2017 roadmap.

A real-world test

Mozilla, which also sponsors the Rust project, has already started shipping Rust components in Firefox. Recently, another high-profile project had the opportunity to explore if Rust could actually replace C. The NTPsec project, which is intended to replace the Network Time Protocol (NTP) reference implementation, has a large code base written in C. Eric Raymond, who is working on NTPsec, has already completed a major cleanup of the existing code. He is also considering a complete rewrite in either Rust or Go.

In theory, Rust looked like a clear winner over Go and could even be a viable replacement for C. Rust is more "C-like", with machine code output, no garbage collection, and a stricter type system than Go. But Raymond concluded that Rust not only did not fit the project's needs, but that in its current state, Rust is unfit for production. The immaturity of the Rust ecosystem and the language syntax created a tax on productivity:

In practice, I found Rust painful to the point of near unusability, The learning curve was far worse than I expected; it took me those four days of struggling with inadequate documentation to write 67 lines of wrapper code for the server.

Even things that should be dirt-simple in Rust, like string concatenation, are unreasonably difficult. The language demands a huge amount of fussy, obscure ritual before you can get anything done.

The contrast with Go is extreme. By four days in of exploring Go I had mastered most of the language, had a working program and tests, and was adding features to taste.

Instead of a community design process, Raymond wonders if the Rust project might benefit from a single "Benevolent Dictator" (or a "core team" of some kind) making decisions. There is some evidence for the need for this in other parts of the language. Rust's type system lays the groundwork for concurrency, but the implementation is provided by libraries and is not part of the core language. According to the Rust documentation, this was a deliberate design decision to give programmers flexibility in their code:

Before we talk about the concurrency features that come with Rust, it's important to understand something: Rust is low-level enough that the vast majority of this is provided by the standard library, not by the language. This means that if you don't like some aspect of the way Rust handles concurrency, you can implement an alternative way of doing things.

Contrast that with the Go language, whose designers chose to use communicating sequential processes as the formal model to manage concurrency in the language. Signals and channels are first class primitives in Go, which makes it relatively easy to pass messages between threads.

The Rust roadmap does include plans to improve this situation. While the standard library is unlikely to change, there is a renewed focus on improving the quality of packages provided by Rust's third party "crates". Concurrency is one of the specific areas the project developers would like to improve. There is also work underway to improve the overall maturity of the ecosystem, including a ranking system for packages.

While Go's focus on programmer productivity may make it a better fit for NTPsec, Raymond hasn't actually committed to rewriting any of the remaining C code. In the meantime, the Rust project is listening to its critics. With the renewed focus on improving the language's usability, Rust is likely to become a more competitive choice for a wider variety of use cases. But that is all in the future. Until then, C will remain a safe bet.

Index entries for this article
GuestArticlesKovsky, Eddie


to post comments

Toward a more approachable Rust

Posted Feb 23, 2017 9:52 UTC (Thu) by oever (subscriber, #987) [Link] (10 responses)

I've been writing quite a bit of Rust in my spare time since half a year. To me the initial learning curve was not high until I hit issues with the borrow checker.

The borrow checker is challenging to learn, but when you understand it, you have not simply learned to appease the borrow checker, you've learned to write good code. Most of the issues that the borrow checker uncovers is unsafe cost. Code that would have been much harder to do right in C and code which I probably would not have done right in C but which might have worked well on the happy path.

Rust has many things that are easier to learn than in C. Macros are nice and sane. Building and packaging is simple. Writing tests is simple and writing code snippets in documentation is simple. In fact, Rust compiles and checks all code snippets in documentation! It even has benchmarking built into the packaging system, cargo.

Of course there are downsides. Some code is quite verbose. Rust does not have function overloading. It does not implicitly convert integer types. But these are trade-offs needed to achieve speed and safety. You have to tell the compiler exactly what you mean.

The most important aspect of Rust for me is that you can trust your code much better. There are way less hidden gotcha's in code. This makes it easier to read other people's code.

Before Rust, my favorite programming language was Haskell. Rust is less elegant in the mathematical sense, but it makes up for that by being very practical and still very fast and safe.

Toward a more approachable Rust

Posted Feb 24, 2017 8:26 UTC (Fri) by marcH (subscriber, #57642) [Link]

> Before Rust, my favorite programming language was Haskell.

https://air.mozilla.org/introduction-to-rust-for-ocaml-pr...

Toward a more approachable Rust

Posted Mar 2, 2017 8:10 UTC (Thu) by ras (subscriber, #33059) [Link] (8 responses)

> Before Rust, my favorite programming language was Haskell.

Which goes some way to explaining why you didn't find the learning curve high at first.

> To me the initial learning curve was not high until I hit issues with the borrow checker.

It is always is that way, isn't it?

I discovered HackerRank, and taught myself Rust by doing a number of problems there - maybe 20, maybe 50 - I'm not sure, but definitely more than the 4 days ESR spent with it. He's about my age, and he if did manage to master the language in 4 days he's a much, much smarter man than I. Haskell and it's type inference isn't something I've come across in any language I am familiar with, so the learning curve was punishing. Even so I grew to like the language, and fell in head over heels in love with the build system. Towards the end I thought I was pretty good with the borrow checker, but one a nagging doubt remained - those practice programs were all very small.

I decided to test myself by doing some serious data structure work with Rust. I general, I could not do it, at least not without lots of unsafe blocks. If I was to summarise the pain point, it would be because the borrow checkers reasoning starts and ends within the stack, mostly within the current stack frame. Use stuff on the heap and things get very difficult very quickly. Try doing tree insertion without using recursion some time - I don't think it's possible. I ended up wishing Rust had some sort of "const" feature for struct's - fields the compiler could know would never change outside of constructors and destructors. That way the bloody borrow checker extend stack lifetimes to pointers within struct's, and so could derive information about the lifetimes on stuff on the heap too.

Still, I was a relative newbie at Rust, so I went along Servo talk. Servo is Mozilla engine written entirely in Rust. In fact it's the largest Rust project and has been a big driver of the language. Naturally it uses the heap a lot, so I wanted to see how they got around code littered with unsafe's. Sadly answer was they hadn't managed it. They were hoping one the top Rust language guru's would look at the worst library and fix it. Maybe he could, but if it took someone like that a mere mortal like myself had no hope in day to day day usage. I just don't have that sort of time at my disposal.

I didn't see the point of going to all that cognitive effort of figuring out how to appease the borrow checker, when for arguably the most dangerous part of memory - the heap, you can't do it. I gave up at that point.

Toward a more approachable Rust

Posted Mar 3, 2017 1:13 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (7 responses)

I've been working on a Rust codebase for a number of months (it's been deployed to production for the last month or so) and I haven't had to write any unsafe code. Granted, there are *lots* of calls out to the git binary (no, libgit2 doesn't support what we need), but the amount of confidence it gives me when I go in and refactor some functionality out for new features is amazing. No worries about rogue null pointers floating underneath the surface, easy to parallelize a loop without fear, easy access to a test suite, and more all have me sold.

Granted, I have also programmed in Haskell previously, but if anything, learning these languages has improved my code in Python, C++, and others because I can now more easily spot potential problems with pointer manipulation and the like.

There are times I fight the borrow checker, but in the end it has (so far) always been right short of the non-lexical lifetime issue (which has a path to resolution now). It lets me specify in my interface that some structure holds a resource necessary for another (e.g., a temp directory which is deleted when it goes out of scope).

What kinds of problems were you trying to solve that you needed so much unsafe code?

Toward a more approachable Rust

Posted Mar 3, 2017 2:41 UTC (Fri) by ras (subscriber, #33059) [Link] (6 responses)

> What kinds of problems were you trying to solve that you needed so much unsafe code?

I may not have been clear. In all the simple stuff I did there was no unsafe code, and I was never tempted reach for it. It was when I grew suspicious that I was solving only simple "Rust friendly" problems, and actively searched for places that it might fall down that the rot set in.

You don't have to look far to find buckets of unsafe code: have a read of the standard library some time. I often did that to figure out how the a rust expert would attack a problem. It was seeing all the unsafe code in there that first raised my suspicions: how was that different to what I was doing?

I gave you the answer: the problems started to arise when you use the heap, explicitly. In particular you have stuff on the heap referencing other objects on the heap. You can avoid explicit use of the heap a lot of the time (for me it was all of the time when doing simple stuff), by using Rusts inbuilt collections. These collections do use the heap internally of course, and as a consequence if you peak inside of them you will see many unsafe blocks. But a person using those collections to store simple objects won't need to use unsafe's. If you are doubting this try writing your own collection with iterators, without using Rusts collections internally. For extra bonus points don't use recursion.

One common "cheat" I've seen used several time (along with "see, you don't need unsafes in Rust") is to put the objects you want to reference into an array, and then store the index into that array. It's cheating because every index lookup does contain an unsafe block, and that unsafe block costs time (the index bounds check) at run time.

The canonical Rust solution to heap pointers to other heap objects it to wrap everything in RefCell's. Aside from leading to some truly ugly type descriptions [0] it means you are absorbing a runtime hit every time you move between pointers. One of the borrow checkers purposes in life is to allow you to move around data structures without incurring that hit by doing the checks at compile time. It works well only when everything is on the stack.

[0] From stdio.rs: Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>>

Toward a more approachable Rust

Posted Mar 3, 2017 13:25 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (5 responses)

This happens with any language with safety guarantees. What unsafe let's you do is point out where tricky business is going on and check that code more carefully. Unsafe code still has to abide by the rules, but it is enforced by developers rather than the compiler. Haskell has the same thing. Want a global state? You need unsafePerformIO. It's just how it's done.

One other reason for unsafe is performance because Rust is also not blind to the realities of hardware. This helps it reach actual use rather than things like Haskell where things like laziness and space leaks become an issue.

I suggest you look at some of the lower libraries like Rayon which has (I believe) just a handful of unsafe bits. Everything else can be checked and verified by the compiler. Also check out the improvements to the sorting algorithms which are 90%+ safe code (last I saw it).

The type you gave describes what it is: thread-shared line buffered output that may or may not exist, can be accessed via reentrant calls. What would it be in C++? Just another pointer with a sidecar mutex. Yep, no way to screw that up.

Yes, you need unsafe somewhere because not everything does, internally, abide by the rules. But when you make those blocks and make the rules conform, the code using it can be reasoned about much more easily. Even if it's not perfect, it's still way better than what is available in C, C++, D, or Go. What else would you suggest these days?

Toward a more approachable Rust

Posted Mar 3, 2017 18:48 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]

> I suggest you look at some of the lower libraries like Rayon which has (I believe) just a handful of unsafe bits.
There's also RustBelt that attempts to prove correctness of the unsafe bits if they are encapsulated correctly.

Toward a more approachable Rust

Posted Mar 4, 2017 3:50 UTC (Sat) by ras (subscriber, #33059) [Link] (3 responses)

> What else would you suggest these days?

I guess what I was looking for is something that I would use instead of Python. I was willing to give up simplicity for that, although it was a bit of a shock to see just how complex things became. "Code flowing off the fingers" is not something I've experienced with Rust. However, I did expect to get more than I actually received in exchange for that complexity.

Perhaps an example would help. Take a doubly linked list implementation. Lets call an element a DListElt. It has a number of operations operations you can perform on it. Lets look at two of them: DListElt.insert() and DListElt.remove(). You can call insert() only when it's not in a list, and remove() only when its in a list. It would be nice to be able to verify this at compile time (thus avoiding the overhead of run time checks, so it would be as fast a C), so for example if you try to call remove() when it's in a list, you get a compile error.

Rust can't do this. What it can do is very limited: it can attach a borrowed state to a type, but this only works for stuff in a stack frame. It's not hard to understand why this stack limitation exists: the compiler needs to know the lifetime of a borrow and it has only two sources of known lifetimes: variables on the stack whose lifetime is the same as the stack frame, statics who live forever. This means the compiler can not reason about anything on the heap. So when confronted with my dinky little DListElt type that only has a grand total of two states which can be represented by a simple boolean (borrow's state is far more complex) that lives on the heap, it can't help. Rust's does have an inbuilt LinkedList of course, but because of this it is burdened with unsafe's and run time checks for most operations.

The frustrating thing for me is it not hard to do. (I've invented some syntax for what follows, hopefully it's meaning is obvious.) Just let the programmer create his own compile time state information, and attach it to a type. Lets invent DListElt:Inuse<bool>. Remove() and insert() then become a casts of sorts. It is defined only for DListElt:Inuse(True).remove(), and returns a DListElt:Inuse(False). Similarly insert() is defined only for DListElt:Inuse(True).insert(), and returns a DListElt:Inuse(False).

Interestingly, a very early version Rust did do something like this but they abandoned the idea in favour of the current borrow syntax. I'm not sure why. I suspect that borrow is all they needed to avoid the bulk of memory corruption issues they saw and they could not come up with generic type system that was powerful enough to implement it. I also suspect most of their examples they looked at were smallish programs, because once you need complex data structures that live on the stack the borrow checkers limitations really bite hard. At least they they bit me hard, and they bit servo too.

So yes, they have come up with a language that occupies some unique niche in the (fast, safe) space. But it's not safe enough to replace Python. It is definitely safer than C / C++, and yes as you say the unsafe blocks show you where to look for the bits that are not. If you do look you will find checks in the unsafe blocks that perform at runtime the checks the compiler could not do at compile time. This is what makes it as safe a Python for simple programs that don't need their own unsafe blocks, but it comes at the cost of making it slower than C and C++.

> I suggest you look at some of the lower libraries like Rayon which has (I believe) just a handful of unsafe bits.

I'd hope not. It just an implementation of thread polling with work stealing. The person who writes the threads (ie, *you*) still has some responsibly for ensuring those threads don't stomp on each other. https://github.com/nikomatsakis/rayon contains this truly weird statement, which to sounds me like he is trying to redefine words to wriggle out of saying just that:

> While there are threadsafe types that offer similar APIs, caution is warranted because, in a threadsafe setting, other threads may "interject" modifications in ways that are not possible in sequential code. While this will never lead to a data race --- that is, you need not fear undefined behavior --- you can certainly still have bugs.

He is talking about multiple threads executing code like this: atomic.set(atomic.get() + 1). It's true only if you use the Rust definition of undefined behaviour: don't reference memory that isn't yours. It sounds like a deliberately knobbled definition to me, but one that happens to line up with what their borrow checker can guarantee, albeit for stack only.

Toward a more approachable Rust

Posted Mar 4, 2017 4:19 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

> DListElt.insert() and DListElt.remove(). You can call insert() only when it's not in a list, and remove() only when its in a list.
The problem here is suspicious design - your entities are both elements of a list and objects in their own right.

So if you hold a reference to any entry you can traverse the whole list. To guaranteed its safety for the Rust's borrow checker you need to make sure that there are no other mutating references to it.

And since it looks like you want elements to live their own livetimes, you really need to have runtime locking that tell Rust's borrow checker that it's OK. I'm not sure how typestate (that was in early version of Rust) is going to help you here.

Toward a more approachable Rust

Posted Mar 4, 2017 9:34 UTC (Sat) by ras (subscriber, #33059) [Link]

> So if you hold a reference to any entry you can traverse the whole list.

As a point of order, no you can't. What you call the entry is called struct Node in linked_list.rs. It is private to LinkedList, and besides has no traits that could define a next() method, or even a way to get to the list head. I presume you meant "if you hold a reference to the LinkedList you can traverse it". That's certainly true (there wouldn't be much point to it otherwise). It's done that way so your next statement holds true:

> To guaranteed its safety for the Rust's borrow checker you need to make sure that there are no other mutating references to it.

Another point of order. There is simply no way to inform Rust's borrow checker you have borrows on all the nodes in the list. Well that's not quite true, you can use recursion. But if you restrict yourself to one stack frame you can have only borrow per local variable so borrowing an arbitrary number of objects is impossible. The implementer of LinkedList is constrained by this too, so he must bypass the borrow checker and wrap all references to Node's in unsafe's. So the guaranteed safety you mention has nothing to do with the borrow checker. It stems purely from you trusting the guy who wrote the LinkList code.

In one way none of this matters. The LinkedList author succeeds in creating the illusion a borrow on the LinkList applies to all the elements on it, so the users of the library can blissfully write code without a single unsafe under that assumption. But it does have it's costs. In the current implementation a loop iterating over all nodes must first create the integrator object on the stack (copy 3 words), and then do the same pointer load the C implementation would do. As a consequence the LinkedList has 0 or 1 nodes the C version will use a fraction of the cycles. But perhaps a different implementation could approach the C version.

Unfortunately, it can't hope to have the flexibility of the traditional C implementation. For example, in a C implementation if you follow pointers in the heap to a node on the linked list removing it is a O(1) operation. For that matter, so would operation you mentioned above: "if you hold a reference to any entry you can traverse the whole list". The Rust implementation doesn't provide either of these operations, at all. If it did provide a "delete this element" function it would be O(length_of_list). I don't see how a Rust library could provide an O(1) delete for a linked list. You could do it yourself of course, but then you end up where I and servo did: code littered with unsafe's.

Which is exactly the point I was making when I first responded to to this series of posts. Try to use Rust to implement a largish program with complex data structures, and you will end up with code littered with unsafes because just having a borrow checker is too limited. I don't think it's a huge improvement on C. There are lots of things that are improvements of course: the wonderful build system, the doco, cargo.io, the community, play.rust-lang. It's pretty extraordinary effort really. But the language itself - it's still underdone.

Toward a more approachable Rust

Posted Mar 4, 2017 12:14 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

> "Code flowing off the fingers" is not something I've experienced with Rust. However, I did expect to get more than I actually received in exchange for that complexity.

Yes, once you get down into implementing specialized structures, you'll need some unsafe, but when properly wrapped up, you shouldn't need it. I've certainly had moments where code "flowed off of the fingers" and all I was left with, once it compiled, was minor fixes rather than things like "did you remember to check for that None that can sneak in here this way?" kind of things.

> Lets call an element a DListElt. It has a number of operations operations you can perform on it. Lets look at two of them: DListElt.insert() and DListElt.remove(). You can call insert() only when it's not in a list, and remove() only when its in a list. It would be nice to be able to verify this at compile time (thus avoiding the overhead of run time checks, so it would be as fast a C), so for example if you try to call remove() when it's in a list, you get a compile error.

This is not idiomatic in Rust anyways. Instead, you make node just the data, possibly with some accessor methods. For insert and remove, you instead make new structures which represent the action and can have their own lifetime associated with it. Look at the Entry structure for a map to see an example of this.

> So yes, they have come up with a language that occupies some unique niche in the (fast, safe) space. But it's not safe enough to replace Python. It is definitely safer than C / C++, and yes as you say the unsafe blocks show you where to look for the bits that are not. If you do look you will find checks in the unsafe blocks that perform at runtime the checks the compiler could not do at compile time. This is what makes it as safe a Python for simple programs that don't need their own unsafe blocks, but it comes at the cost of making it slower than C and C++.

Ha! Python has all kinds of unsafeties from its type system that even C++ will catch. Rust has better inference, so things are more ergonomic with the type system than C++ with things like template-derived types and the like.

> I'd hope not. It just an implementation of thread polling with work stealing. The person who writes the threads (ie, *you*) still has some responsibly for ensuring those threads don't stomp on each other.

Anything stronger would (ISTM) require a proof system because you'll always have a way to escape unless you go the Erlang route and just say "nope, can't assume an OS, so you have to use Erlang threads instead of native threads" which isn't really suitable for a language that you'd like to run directly on hardware (unless the Erlang runtime is a suitable OS I suppose).

It seems to me that you're arguing from a "you need unsafe in one place, so the whole thing is invalid" whereas those kinds of things usually go into their own libraries with test suites and should be hiding all of the unsafe blocks from you. You're also expecting magic (proof systems) instead of what Rust does guarantee: write your unsafe code (usually, data structures) carefully and your use of that can be checked and enforced by the compiler with no overhead (that is, no ASan or TSan necessary).

Toward a more approachable Rust

Posted Feb 23, 2017 10:26 UTC (Thu) by anselm (subscriber, #2796) [Link] (1 responses)

Raymond wonders if the Rust project might benefit from a single "Benevolent Dictator" (or a "core team" of some kind) making decisions.

Time for esr to fork Rust and take over?

Toward a more approachable Rust

Posted Feb 23, 2017 19:31 UTC (Thu) by Sesse (subscriber, #53779) [Link]

I'm sure the Rust community would gravitate to such a fork very quickly. Not.

Toward a more approachable Rust

Posted Feb 23, 2017 12:03 UTC (Thu) by peter-b (subscriber, #66996) [Link] (21 responses)

> While Go's focus on programmer productivity may make it a better fit for NTPsec, Raymond hasn't actually committed to rewriting any of the remaining C code. In the meantime, the Rust project is listening to its critics. With the renewed focus on improving the language's usability, Rust is likely to become a more competitive choice for a wider variety of use cases. But that is all in the future. Until then, C will remain a safe bet.

Given the horrendous rate at which serious, exploitable defects are being routinely found in C codebases, and the complete absence of any features in C to reliably prevent them from being introduced, I believe it's irresponsible (at best) to start any new projects in C in 2017.

Toward a more approachable Rust

Posted Feb 23, 2017 13:12 UTC (Thu) by jnareb (subscriber, #46500) [Link] (11 responses)

NTPsec started with NTP Classic codebase in C (then removed much of obsolete code, and improved its quality).

Toward a more approachable Rust

Posted Feb 23, 2017 16:40 UTC (Thu) by jubal (subscriber, #67202) [Link] (10 responses)

This is a common soundbite, but I've yet to see any proof of that.

Toward a more approachable Rust

Posted Feb 23, 2017 19:04 UTC (Thu) by Beolach (guest, #77384) [Link] (9 responses)

From ESR's blog: NTPsec dodges 8 of 11 CVEs because we’d pre-hardened the code.

Toward a more approachable Rust

Posted Feb 23, 2017 19:36 UTC (Thu) by jubal (subscriber, #67202) [Link]

You can find many things on ESR's blog.

Toward a more approachable Rust

Posted Feb 23, 2017 20:55 UTC (Thu) by pizza (subscriber, #46) [Link] (7 responses)

...One person's "bloat" is another person's "essential feature".

Toward a more approachable Rust

Posted Feb 23, 2017 21:30 UTC (Thu) by Beolach (guest, #77384) [Link] (6 responses)

Of course. And to quote NTPsec's Removal Plans page:
If something on this list is important to you, tell us. If the complexity cost of keeping it is low, you win. If the complexity cost is high, then we will need a donation of engineering time or money to support keeping it in the codebase.
If your essential feature is on that list & you can't convince NTPsec to agree, then keep using NTP Classic. Or Chrony or OpenNTPD or whatever does still support your essential feature. Personally I really like having multiple implementations to choose from.

Toward a more approachable Rust

Posted Feb 23, 2017 23:40 UTC (Thu) by pizza (subscriber, #46) [Link] (5 responses)

Sure, that's a reasonable attitude to take.

But it is rather disingenuous to claim that NTPsec is superior to NTPd because the latter has bugs in features that the former lacks altogether.

Toward a more approachable Rust

Posted Feb 24, 2017 1:16 UTC (Fri) by Beolach (guest, #77384) [Link] (4 responses)

I disagree. Specifically for NTPsec, at least some of the "features" removed don't work in NTP Classic anyway, at least according to NTPsec on that same Removal Plans page. Autokey & ntpsnmpd are maybe debatable (for me they're not close to essential features & I'm not informed enough on them to debate whether or not they work in NTP Classic myself), but look at the reasons they give for removing the refclocks - I have a hard time seeing how all those refclocks would work in NTP Classic either, so keeping the code serves no purpose. And I could be misremembering, but I believe at least 1 of the "8 CVEs dodged" was from a refclock driver.

But also in general principle, I strongly believe in minimalism/KISS/UNIX philosophy. If you say my bloat is your essential feature, fine - I'm perfectly willing to believe you, and I hope you can find or create an implementation that does include your feature & works for you (and as I said above, I really do like multiple implementations, and I view monopolies as unhealthy). But I'm also going to emphasize that the converse is equally true - one person's "essential feature" is another's "bloat", and just because I'm willing to believe it is essential to you, doesn't mean I'm willing to accept it being forced on me. And from reading the bit I quoted above, I think NTPsec is being quite reasonable about listening to arguments about what is "essential". So no, I don't think it's "disingenuous to claim that NTPsec is superior to NTPd because the latter has bugs in features that the former lacks altogether."

Toward a more approachable Rust

Posted Feb 24, 2017 11:16 UTC (Fri) by jubal (subscriber, #67202) [Link] (3 responses)

Can we please stop using the terminology ESR coined to help himself hijack another project? Thanks in advance.

Toward a more approachable Rust

Posted Feb 24, 2017 20:40 UTC (Fri) by flussence (guest, #85566) [Link] (2 responses)

You seem very invested in this conversation, despite the fact you haven't done anything but insult others in it...

Toward a more approachable Rust

Posted Feb 25, 2017 1:27 UTC (Sat) by jubal (subscriber, #67202) [Link] (1 responses)

While my opinion of ESR (both of his professional qualities and his social graces) is indeed rather low, but I don't think I have insulted anyone here.

Toward a more approachable Rust

Posted Feb 26, 2017 2:49 UTC (Sun) by ncm (guest, #165) [Link]

I agree. It reflects well on correspondents here that ESR gets more courtesy than he deserves. Courtesy need not extend to adopting his framing.

Toward a more approachable Rust

Posted Feb 23, 2017 14:49 UTC (Thu) by tshow (subscriber, #6411) [Link] (8 responses)

By a similar argument, because there are orders of magnitude more drownings in water than ammonia or acetic acid, it would be irresponsible to fill a new swimming pool with water in 2017.

C definitely wasn't built with security in mind, and there are definitely problems with it, but "C codebases" are also the majority of the attack surface out there.

Toward a more approachable Rust

Posted Feb 23, 2017 15:34 UTC (Thu) by pizza (subscriber, #46) [Link] (7 responses)

> C definitely wasn't built with security in mind, and there are definitely problems with it, but "C codebases" are also the majority of the attack surface out there.

Are you sure about that? I mean, we're in a bit of a C echo chamber here, but large-scale data breaches are almost never due to anything C-related (ie a buffer overflow) -- instead things like not changing default/hardcoded passwords, network sniffing of insecure connections, SQL insertion, cookie hijacking (and other XSS-type stuffs), and so forth -- and of course let's not forget the "disgruntled employee" who just copies everyhing onto a USB stick and walks out the door...

Toward a more approachable Rust

Posted Feb 23, 2017 17:54 UTC (Thu) by tshow (subscriber, #6411) [Link]

Right, but you aren't fixing any of those by switching to Rust either. We're presumably talking about the subset of security bugs that can be fixed with a "better" programming language.

Toward a more approachable Rust

Posted Feb 24, 2017 8:26 UTC (Fri) by peter-b (subscriber, #66996) [Link] (2 responses)

> Are you sure about that? I mean, we're in a bit of a C echo chamber here, but large-scale data breaches are almost never due to anything C-related (ie a buffer overflow)

"Almost never"

https://blog.cloudflare.com/incident-report-on-memory-lea...

Toward a more approachable Rust

Posted Feb 24, 2017 12:35 UTC (Fri) by pizza (subscriber, #46) [Link] (1 responses)

Okay, what part of "large scale data breach" applies here? Couldfare's bug _could_ have led to some data being leaked, but I'm finding it hard to imagine that happening on any sort (organized) scale.

C buffer overflows weren't responsible for 1 billion Yahoo accounts having their credentials compromised. They weren't responsible for the multiple point-of-sale compromises that were responsible for my credit cards being revoked three times in a single calendar year (including one actual fraud attempt) Buffer overflows weren't responsible for the Federal OMB's compromise of literally millions of highly sensitive personnel files. They aren't responsible for the Classified info leaked by Snowden or Manning. More down to earth, Buffer overflows weren't responsible for the Murai botnet with upwards of half a million webcams compromised by a fixed backdoor that yielded total access. Buffer overflows have nothing to dow with folks [re]using weak passwords on every online acount. Buffer overflows don't matter at all to [spear] phishing that deliberately targets folks with keys to the kingdom, or systems with "special managment exceptions" because executives can't be bothered to abide by the same security procedures as their underlings.

...And so on.

Sure, at an individual level those overflows can (and do) suck, and we should absolutely fix them. But let's not delude ourselves into thinking that getting rid of C sill have _any_ effect on piss-poor system design and the utter fallibility of the person sitting between the keyboard and the chair.

Toward a more approachable Rust

Posted Feb 24, 2017 12:43 UTC (Fri) by pizza (subscriber, #46) [Link]

Upon reflection I suppose we don't know what led to the Yahoo breach -- not even they know, apparently -- but the silver lining there is that there's finally a tangible cost to poor security practices -- about $0.35/user, which in aggregate is large enough for the suits to start caring.

Toward a more approachable Rust

Posted Feb 25, 2017 5:17 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

Your timing was simply impeccable: https://lwn.net/Articles/715535/

Toward a more approachable Rust

Posted Feb 25, 2017 12:40 UTC (Sat) by pizza (subscriber, #46) [Link] (1 responses)

Yeah, it was just about perfect timing :)

But on a more serious note -- Due to the nature of how the Cloudfare CDN caching/proxying/rewriting works, this bug seems to be more of an information leak of random data rather than something that can be realistically harnessed/targeted to extract specific, targeted information, as it is heavily dependent on:

1) The exact page contents of what you're requesting (Granted, if you can control the contents of the page being cached, you could conceivably construct pages to maximixe this)
2) The other page requests that were served before yours and their contents, size, timing, and whatnot. Given the scale of the Cloudfare CDN, this is the sticking point..

It's not like heartbleed where you could just hammer a single, specific, targeted server again and again and adjust the parameters until you saw something interesting.

...Am I wrong in my interpretation?

Granted, with sufficient time, data capture, and a considerable amount of analysis, one could probably map the random extra stuff one was seeing to at least specific domains, which might enable some targets of opportunity -- eg a specific user session (cookie hijacking), perhaps a random user's credentials if you're really lucky, and possibly even a MITMing of an entire domain if you're able to extract its private TLS key in drips and drabs.

It's lousy, but it's not "our entire user database has been downloaded without anyone knowing" lousy.

Toward a more approachable Rust

Posted Feb 25, 2017 16:43 UTC (Sat) by excors (subscriber, #95769) [Link]

> if you can control the contents of the page being cached

That part seems trivial: sign up as a Cloudflare customer and publish a page that triggers the parser bug.

> The other page requests that were served before yours and their contents, size, timing, and whatnot. Given the scale of the Cloudfare CDN, this is the sticking point..

If you want to target users of a specific site, I guess you could perhaps write a page like:

<script>
var xhr1 = new XMLHttpRequest();
xhr1.open("GET", "https://very-secure-site-using-cloudflare.example/");
xhr1.withCredentials = true;
xhr1.send();

var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "page-that-triggers-the-parser-bug.html");
xhr2. { /* send the content back to the attacker */ }
xhr2.send();
</script>

and host it via Cloudflare, and make users run that script (e.g. distribute it through an ad network).

If the user is already logged into the target site, the first request will send their credentials (cookies etc) over HTTPS, which will hopefully end up unencrypted in the Cloudflare cache server's memory. (The XHR security model assumes GETs don't trigger side effects, and it won't allow the attacker to read the cross-origin response, so that request would normally be safe). The second request will trigger the parser bug, Cloudflare will append random memory content to the response, and (since it's a same-origin request) the attacker can read that response.

Since the two requests are being made by the same user there's a reasonable chance they'll end up going through the same cache server, and since the requests are nearly simultaneous there's a reasonable chance the leaked data will come from the corresponding first request instead of being mixed up with other users' requests. Repeat a few million times until success.

Translation distance

Posted Feb 23, 2017 13:32 UTC (Thu) by moltonel (guest, #45207) [Link] (4 responses)

To me one of the best pro-Rust argument when converting a C project is that you can do it piecemeal, down to the individual function level, so I was surprised that https://blog.ntpsec.org/2017/01/18/rust-vs-go.html didn't take that into account. Having an automated C-to-Foo converter is great, but being able to refactor rather than rewrite (and not halt all other development while the conversion is in progress) is IMHO much more important.

I'm also not sure about the "adding Rust’s ownership/lifetime annotations to C would be a huge job" argument: there are plenty of impedance mismatches when going from C to either Rust or Go, and the task of adding annotations seems rather minor compared to (for example but without holding that against Go) starting to use channels and goroutines for concurrency.

Having played with both Go and Rust, it's clear to me that Go is easier to start with, but Rust remains much more compelling due to its design, philosophy, community. I look forward to a more approachable Rust, but there's little reason to hold off starting to use Rust today.

Translation distance

Posted Feb 23, 2017 14:42 UTC (Thu) by tshow (subscriber, #6411) [Link] (1 responses)

Without care, though, this can turn into "a REAL programmer can write FORTRAN in any language". The way you structure a program in a strongly-typed system ought to be fairly different from the way you structure it in a loosely typed language like C, even at function granularity.

Translation distance

Posted Feb 23, 2017 15:35 UTC (Thu) by moltonel (guest, #45207) [Link]

Yes, converting without restructuring will prevent you from getting the full benefit of the new language. And while corrode and the possibility of function-at-a-time conversion can let you do that, I don't think that they're pushing towards it. The mindset of the person doing the conversion is more important. And when I read ESR's comment about adding lifetime annotations, I fear that he's in a "very straightforward conversion" mindset, or that he only gave Rust a very superficial look (sadly with current Rust, you can only achieve that much in your first 4 days).

Translation distance

Posted Feb 23, 2017 16:22 UTC (Thu) by NickeZ (guest, #100097) [Link]

I think Federico is doing some very interesting work with mixing C and rust in the librsvg project. https://people.gnome.org/~federico/news-2017-02.html#17

Translation distance

Posted Feb 27, 2017 18:04 UTC (Mon) by davidstrauss (guest, #85867) [Link]

> To me one of the best pro-Rust argument when converting a C project is that you can do it piecemeal, down to the individual function level

Some types of concurrency, like a fork-based worker model, preclude Rust conversion. It's not only a challenge of linking and stack integration.

Engineering

Posted Feb 24, 2017 4:55 UTC (Fri) by gerdesj (subscriber, #5446) [Link] (3 responses)

"it took me those four days of struggling"

I ended up in IT (long story, short) due to the last recession in the UK. Not the 2008 shenanigans but the one in 1991ish. I should have been a Civil Engineer but I graduated at the same time as the UK building industry decided to take a bit of a time out. Before that unfortunate event, I spent bloody ages learning stuff.

I discovered the joys of how to chase stresses and strains around a structure. I also learned a few other things, not least of which was how many bloody super and subscripts a differential equation can have with regards bits of concrete and steel. Wood is a bit different but still quantitative(ish).

I don't think that you can spend four days with a new concept or system or whatever and decide that it is rubbish. I think that what Raymond demonstrated here was a malaise particular to to IT (this is in no way directed at Raymond personally).

Why on earth is there not a clear and unambiguous way of programming a computer without resorting to machine code? Why can't you clever buggers give us laymen a language we can use? You've had over 40 years to play with.

I'll learn C in the interim - it seems to be designed by Engineers (sharp edges - wear gloves).

Cheers
Jon

Engineering

Posted Feb 24, 2017 5:50 UTC (Fri) by neilbrown (subscriber, #359) [Link]

> Why on earth is there not a clear and unambiguous way of programming a computer without resorting to machine code?

The elephant in the room here is that you are not a rational being, any more than I am. You can fake it a lot of the time, but underneath you are instincts and intuition and hunches and past experience and approximations. All of this works amazing well when working with wide error margins. But in the digital world, if you want error margins, you need to explicitly design them in.

Well.. to be fair, a little bit of you probably is rational, but not enough to be as productive as you want without help from the other bits.

The only thing that isn't "clear and unambiguous" here is the human psyche.

Engineering

Posted Feb 25, 2017 18:10 UTC (Sat) by shmget (guest, #58347) [Link] (1 responses)

"Why on earth is there not a clear and unambiguous way of programming a computer "
there is, but human are all but 'clear and ambiguous'.

Furthermore, in IT, contrary to civil engineering, we usually do not afford the luxury of having a 3x to 10x 'safety' factor to hide the bugs.

Engineering

Posted Mar 1, 2017 14:50 UTC (Wed) by anton (subscriber, #25547) [Link]

There are lots of applications that can and do afford resource usages (CPU, memory consumption) beyond the necessary minimum by these or much larger factors. A problem we have in computing is that that does not necessarily help safety.

Toward a more approachable Rust

Posted Feb 24, 2017 16:30 UTC (Fri) by excors (subscriber, #95769) [Link] (1 responses)

> Rust's type system lays the groundwork for concurrency, but the implementation is provided by libraries and is not part of the core language.

It seems many philosophical differences in Rust vs Go are similar to those in C++ vs Java. E.g. C++ doesn't really support strings in its core language; instead the language provides sufficiently powerful features for the standard library to implement a reasonable std::string type. In contrast, Java has explicit language support for strings: string literals are always java.lang.String objects, the "+" operator is overloaded for java.lang.String and nothing else, etc.

If you just want to use native strings, maybe the Java approach is a little nicer; you can write "foo"+"bar" and get sensible behaviour, whereas C++ needs you to express a conversion to std::string first. The bigger difference is when you *don't* want to use the native strings. You might realise that java.lang.String is UTF-16 which nowadays is a stupid representation for strings, but there's absolutely nothing you can do about it. It's a historical mistake in the language design that application developers are stuck with forever.

In C++, since there's nothing special about the standard library, you can easily replace std::string with your own better string type for your particular use case (and many large applications do so, often with multiple string types optimised for different uses). If you want to use a validity-checked UTF-8 representation, you can do that. If you want to automatically switch between ASCII and UTF-32 representations depending on the content of the string, that's fine. If you want to use a rope data structure, no problem. You have access to operator overloading and templates and implicit constructors over string literals and so on, so your new type is not disadvantaged compared to std::string.

Similarly, Go has a built-in map type with special language support. If you want something slightly different, e.g. an ordered map or a thread-safe one or a higher-performance one, then (as far as I'm aware) you have to implement a custom type that can only support syntax like foo.Get("bar") and not foo["bar"], so it's syntactically inferior to the built-in type. In C++ and Rust the standard library has hash maps, but they're not a special part of the core language, so you can reimplement them without being demoted to a second-class citizen.

For average unadventurous programs, the Java/Go approach of trusting the language designers to provide decent data structures and concurrency features is probably fine; they're pretty smart people and it'd take a lot of effort to do better. But it's likely they made tradeoffs that aren't optimal for your specific application, or decisions that turn out to be mistakes with a couple of decades of hindsight. Even if you're smart enough and willing to put in the effort to do better, the language constrains you. The C++/Rust approach of having a small but powerful core language, with most features going into libraries that application developers can rewrite and replace when necessary, removes that constraint - if your application is valuable enough to deserve the extra investment of effort then you can keep improving it and get a better result in the long term.

(But NTPsec sounds (based on very limited knowledge) like a fairly small program without demanding performance requirements that sits in Go's primary niche of networked servers, and its developers are coming from C not C++, so it probably wouldn't get the benefits of Rust's philosophy and may do better with Go.)

Toward a more approachable Rust

Posted Feb 27, 2017 18:41 UTC (Mon) by davidstrauss (guest, #85867) [Link]

> If you want to use a rope data structure, no problem.

The memory model of encapsulated data is becoming more formalized in C++17 with contiguous iterators. If code consuming a data structure expects to use such iterators (as they might choose to with std::string), you would not be able convert the underlying structure to use a rope.

This is not usually a problem for existing code, as prior standards for C++ had no concept of a data structure promising contiguous memory, but std::string and similar STL data structures have guaranteed that model in a way that hasn't been compiler-enforced (but that programmers could assume). The danger here is updating old code that relies on the properties of something like std::vector with something that doesn't guarantee contiguous iterator use. I could see this happening because the pre-C++17 code had no way to formally require such properties from a data structure (and so one might assume code not requiring it in the C++17 way doesn't need it).

https://isocpp.org/files/papers/n4284.html

Toward a more approachable Rust

Posted Mar 2, 2017 16:16 UTC (Thu) by kjp (guest, #39639) [Link] (6 responses)

I'm very sad that swift still isn't anywhere in the discussion. To me, swift seems like a good candidate for this. It wouldn't have memory overhead (good for a constantly running daemon) and the cycles you burn for refcounting wouldn't matter for something with as little load as an ntp client. To be honest, I think google is setting back programming by decades by pushing GO - if you want to use go, just use java 1.0 without exceptions.

Toward a more approachable Rust

Posted Mar 3, 2017 1:16 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (1 responses)

Does Swift have a stable command line interface yet? Or does it still require driving by Xcode or some other external tool? I seem to remember some parts of swiftc being black boxes for calling (though I only lightly followed the thread seeking to add support for it to CMake).

Toward a more approachable Rust

Posted Mar 4, 2017 11:45 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

Looking at CMake, Swift support is there for Xcode, but not other generators.

Toward a more approachable Rust

Posted Mar 3, 2017 13:07 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

Toward a more approachable Rust

Posted Mar 3, 2017 14:46 UTC (Fri) by karkhaz (subscriber, #99844) [Link] (2 responses)

As far as I know, Java (1.0 or otherwise) is using shared-memory concurrency rather than channel-based (message passing)?

The two are theoretically equivalent in terms of their expressivity, but shared memory is known to trip programmers up a lot more---deadlocks, races, all the bad concurrency bugs that don't typically occur when you're using channel-based concurrency.

Go's big win is bringing this sort of concurrency to a (hopefully soon-to-be) mainstream language. The language that came closest to this, I suppose, is Erlang, but that's still fairly niche. Channel-based concurrency is apparently the canonical style in Rust also (AFAIK), although in that language it's implemented through the library rather than as language primitives.

Toward a more approachable Rust

Posted Mar 3, 2017 16:34 UTC (Fri) by kjp (guest, #39639) [Link] (1 responses)

What is stopping you from passing a message in java? Or in any language?

Toward a more approachable Rust

Posted Mar 3, 2017 17:36 UTC (Fri) by karkhaz (subscriber, #99844) [Link]

The lack of primitive support. I was mostly arguing against your assertion that Go "is setting back programming by decades". Quite the contrary---I'm hoping that future languages follow Go's trend of having channel-based concurrency as part of the language model.

Obviously you can do anything in any language and you can write a kernel in javascript and a web application in OpenCL if you really want to. But having domain-specific linguistic constructs shape our perception of how to think about solving the problem at hand. Nothing is _stopping_ you from passing messages around in Java, but there isn't anything that makes it particularly compelling either. The Thread/Mutex/etc classes in the Standard Library are mostly modelled after pthreads, and there isn't any Standard Library interthread channel implementation AFAIK---never mind language support.

It would be great if tomorrow's programmers end up thinking about concurrency in a message-passing style, rather than shared-memory style, by default. Having languages that express these ideas as primitives will go a long way toward ensuring that this happens. Pthread-style concurrency should be something that you only learn if you have to use it, rather than the default way of doing concurrency. Go is a pioneer in this regard (well, Erlang is even more of a pioneer, but it never caught on).


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds