5 ms·
Okay, finally I get it! As far as I know, Rust has a few types in the stdlib that are guaranteed to overflow, that you can use if that's what you want. C# has
by marvy 10y ago
Okay, finally I get it! As far as I know, Rust has a few types in the stdlib that are guaranteed to overflow, that you can use if that's what you want. C# has something even more interesting:
checked(a+b) // crash on overflow
unchecked(a+b) // wrap on overflow
a+b // use the default of the surrounding context
The global default is provided by a compiler switch. Here's the thing though: the default setting of that compiler switch is to wrap on overflow. Even in debug mode! And you yourself identified the cause: most of the time, you're not doing crypto and such; most of the time, you do not want wrapping. But if you make overflow checking "opt-out" instead of "opt-in", the program slows down.
- akkartik 10y agoMost interesting. Yeah, I'm inclined to just eat that one-time slowdown. We've all anchored (https://en.wikipedia.org/wiki/Anchoring https://en.wikipedia.org/wiki/Anchoring) on running our programs without these runtime checks, so that our apps seem slower with them. But if we take on that one-time cost the programs will eventually get faster with faster hardware, better compilers, more cores, etc. And we'll be safe from these issues for ever more. Thanks for letting me know my idea finally got across. I was massively under-estimating its subtlety.
- marvy 10y ago> Yeah, I'm inclined to just eat that one-time slowdown. Maybe eventually the world will agree with you. We already eat the slowdown that comes with bounds-checked arrays. And indeed, compilers are getting better at removing that bit of overhead, compared to, say, the 1970s.
- akkartik 10y agoSince I have you here: I teach programming with this portable, safe assembly language (as opposed to the portable, expressive assembly language that was C). It has bounds-checked arrays. It always initializes variables. The type system is strong, so you can't ever create an address out of a number. It compiles with -ftrapv so any signed overflow immediately triggers an unrecoverable error. It has pointers and requires manual memory management, but it has refcounts so you can't ever have a use-after-free error. As a consequence any copy of an address incurs some overhead to update refcounts, and any copy of a struct incurs even more overhead to update refcounts of all addresses contained within. Between initialization, bounds checking, overflow checking and refcounting, I think I've pretty much eliminated all sources of undefined behavior. http://akkartik.name/post/mu http://akkartik.name/post/mu http://akkartik.name/about http://akkartik.name/about https://github.com/akkartik/mu https://github.com/akkartik/mu
- marvy 10y agoI've superficially glanced at mu. It seems to have even more awesome stuff than I realized. But still: you're basically programming in glorified assembly. You can't add three numbers without splitting the thing into two statements. Is this really a pleasant way to program? I think this may be a good way to teach programming, but only because no one writes big programs when starting out. On the other hand, you claim to have written the UI in mu itself, so maybe I'm just plain wrong here. (Or did I misread that?)
- akkartik 10y agoYou're right that I wouldn't want the whole world to program this way. That said, it's been surprisingly ergonomic. If it had a compiler and could generate binaries, and if it had parity in libraries with C, I think I would strongly prefer using it to C. The massively increased safety is worth giving up a lot of expressivity for, IMO. No comparison with HLL languages for prototyping, of course. Eventually I hope to get the usual HLLs reimplemented atop it. One subtle point is that since it's refcounted at the Assembly level, any language built atop it requires minimal GC support. The fact that it's Assembly has caused some confusion with others, so I want to point out that: a) you can add any number of numbers in a single instruction; that gets handled behind the scenes. Many instructions are variadic where it makes sense. b) you don't need push/pop/call to call a function, function calls look just like primitive operations. c) Mu knows about types, so it knows how to allocate them, how to print them, and (eventually) how to serialize/unserialize them. These things can be overridden, but they work out of the box for any new types you create. d) Mu supports generic types. Here's a generic linked-list, for example: http://akkartik.github.io/mu/html/064list.mu.html http://akkartik.github.io/mu/html/064list.mu.html. Types starting with underscores are type parameters. e) You get structured programming by default using the break and loop instructions which translate to jumps just after the enclosing '}' and '{' label, respectively. Factorial in Mu looks surprisingly clean: http://akkartik.github.io/mu/html/factorial.mu.html http://akkartik.github.io/mu/html/factorial.mu.html What's common to these features is that I could imagine implementing them in a compiler using just machine language and Mu. What I'm trying to avoid is having to write a complex optimizer. These features (I think) don't massively increase the impedance mismatch between Mu and machine code; they can be implemented with basically glorified search-and-replace. The Mu editor is built in Mu, yes. That's just because I don't have a HLL yet. It's not to say that I think everyone should build apps only in the Assembly layer. It's a little like the early days of C and Unix: until the language got stable people built the original versions of compiler and OS in Assembly. But they were eventually able to move to C.