1. 27
  1.  

  2. 10

    He called out lobste.rs for providing helpful criticism. Good job fellow lobsters!!

    1. 7

      I’ve been guilty of trash-talking other projects myself in the past

      Well, the blog is titled “Software is Crap” :)

      [Rust’s] designers made the unfortunate choice of having memory allocation failure cause termination – which is perhaps ok for some applications, but not in general for system programs, and certainly not for init

      Rust can help with not allocating at all (e.g. heapless), and try_reserve is in nightly already.

      Zig though is a language oriented exactly at this: it forces you to manually pick an allocator and handle allocation failure. But it is much younger than Rust, so if you’re worried about Rust “mutating” (FYI, Rust 1.x is stable, as in backwards compatible), it’s way too early to consider Zig (0.x).

      non-Linux OSes are always going to be Rust’s second-class citizens

      Yeah, related to that: Rust’s designers made the unfortunate assumption that OSes don’t break userspace from one release to another, just like Linux. The target extension RFC would solve this.

      Other than that… while the core team is indeed focused on the “big three” (Linux/Windows/Mac), Rust does support many “unusual” targets, including Haiku, Fuchsia, Redox, CloudABI.

      Back to inits and service managers/supervisors:

      There are so many of them, many of them are interesting (I’ve been looking at immortal recently), but they all have one big problem: existing service files/scripts on your system are not written for them. So I usually end up just using FreeBSD’s rc for basic pre-packaged daemons + runit for my own custom stuff.

      The Ideal Service Manager™ should:

      • read existing service definitions from system packages (rc scripts, OpenRC scripts, systemd units, daemontools/runit/s6 style bare shell scripts)
      • prevent the services from daemonizing, somehow (injecting -f into $SERVICE_flags? horrible and evil hacks like LD_PRELOADing a library that overrides libc’s daemon() with a no-op? lol)
      • force the services to log to syslog, somehow (redirect stdout/stderr, but what about daemons that open a custom logfile by default? maybe just let them do that)
      • supervise them like runit does

      I guess instead of preventing forking it can support tracking forking services with cgroups on Linux, and… with path=/ ip4=inherit ip6=inherit sysvmsg=inherit ... jails on FreeBSD? I wish there was a 100% reliable way to make sure any service runs in the foreground.

      1. 5

        Well, the blog is titled “Software is Crap” :)

        Yeah, there is that. I had originally wanted to emulate a humorous style I’d seen elsewhere (the long defunct “bileblog”) which badmouthed things in such an over-the-top fashion that you knew it was humorous; I could never quite get that right and it always seemed like I was just being nasty. Now I just try to provide objective criticism; it’s probably not as entertaining to read, but it’s also less likely to upset people. And of course, I also write about Dinit and occasionally write (hopefully) helpful articles on other topics.

        Rust can help with not allocating at all (e.g. heapless), and try_reserve is in nightly already. Zig though is a language oriented exactly at this:

        heapless probably wouldn’t serve my needs, but things like try_reserve are what are sorely needed for Rust to be a serious systems language, so I’m glad that’s happening. There are other reasons (perhaps more subjective) that I don’t like Rust - particular aspects of its syntax and semantics bother me - but in general I think the concept of ownership and lifetime as part of type are worthwhile. I have no doubt that good things will come from Rust.

        As for Zig, I need to look at it again. It certainly also has promise; but you’re right that I’d be worried about its stability and future.

        I guess instead of preventing forking it can support tracking forking services with cgroups on Linux, and… with path=/ ip4=inherit ip6=inherit sysvmsg=inherit … jails on FreeBSD? I wish there was a 100% reliable way to make sure any service runs in the foreground.

        Yeah, that’s a fundamental problem. Linux and DragonFlyBSD both have a simple means to prevent re-parenting past a particular process, which is one potential way to solve it (if you are ok with inserting an intermediate process, and really I don’t think that’s a big deal); cgroups/jails as you mention are another; any other option starts to feel pretty hacky (upstart apparently used ptrace to track forks, but that really feels like abuse of the mechanism to me).

        Thanks for your comments.

        1. 5

          Yeah, there is that. I had originally wanted to emulate a humorous style I’d seen elsewhere (the long defunct “bileblog”) which badmouthed things in such an over-the-top fashion that you knew it was humorous; I could never quite get that right and it always seemed like I was just being nasty. Now I just try to provide objective criticism; it’s probably not as entertaining to read, but it’s also less likely to upset people. And of course, I also write about Dinit and occasionally write (hopefully) helpful articles on other topics.

          The problem with it is: that style of humor is so common in the programming world that even good one is not at all novel. Also, as you say it, it’s also very hard to get right, even for seasoned comedians, which - no offense - most programmer aren’t.

          heapless probably wouldn’t serve my needs, but things like try_reserve are what are sorely needed for Rust to be a serious systems language, so I’m glad that’s happening.

          Everyone attaches their own meaning to “systems language”, and adding “serious” feels a bit like moving goalposts. “Ah, yeah, you got the systems part down, but how about serious”. It might not be convenient at all places and I agree that some things are undone, but we’re up against literally decades old languages. We’re definitely serious about getting that issue solved in a foreseeable timeframe.

          Heapless helps in the sense that you can provide your own stuff on top. Even the basic Box type in Rust is not part of libcore, but libstd.

          Servo takes a middle ground of extending Vec with fallible push. (https://github.com/servo/servo/blob/master/components/fallible/lib.rs)

          The thing here is mostly that stdlibs collection considers allocation failure and unrecoverable error. For ergonomic reasons, that’s a good pick for a standard library.

          So, it’s perfectly feasible to write your own collection library (or, for example extension) even now.

          Also, here’s a list of notes about what’s needed to make fallible stuff in the language proper cool. I can assure you after attending the All Hands that this is definitely a hot topic, but also a hard one.

          This just as a little bit of context, I’m not trying to convince you.

          I’d be very interested in what your semantic issues with Rust are.

          To add to that, I’m happy that you took a look at the language, even if you came away wanting.

          As for Zig, I need to look at it again. It certainly also has promise; but you’re right that I’d be worried about its atability and future.

          I’m definitely hoping for more “new generation” systems programming languages. I think there is quite some space around and I hope that some of these make it.

          1. 4

            I’d be very interested in what your semantic issues with Rust are.

            A proper answer to that would need me to sit down for an hour (or more) and go through again the material on Rust to remember the issues I had. Some of them aren’t very significant, some of them are definitely subjective. I should qualify: I’ve barely actually used Rust, just looked at it a number of times and had second-hand exposure via friends who’ve been using it extensively. The main thing I can remember off the top of my head that I didn’t like is that you get move semantics by default when passing objects to functions, except when the type implements the Copyable trait (in which case you get a copy), so the presence or absence of a trait changes the semantics of an operation. This is subtle and, potentially, confusing (though the error message is pretty direct). I’d rather have a syntactic distinction in the function call syntax to specify “I want this parameter moved” vs copied.

            Other things that bother me are lack of exceptions (I realise this was most likely a design decision, just not one that I agree with) and limited metaprogramming (the “hygienic macro” facility, when I looked at it, appeared a bit half-baked; but then, I’m comparing to C++ which has very extensive metaprogramming facilities, even if they have awful syntax).

            I can assure you after attending the All Hands that this is definitely a hot topic, but also a hard one.

            Yep, understood.

            I’m happy that you took a look at the language, even if you came away wanting.

            I’ll be continuing to watch closely. I’m very interested in Rust. I honestly think that some of the ideas it’s brought to the table will change the way future languages are designed.

            1. 3

              …you get move semantics by default when passing objects to functions, except when the type implements the Copyable trait (in which case you get a copy), so the presence or absence of a trait changes the semantics of an operation.

              I can definitely understand how that would feel worrying, but in practice it’s not so bad: Rust doesn’t have copy constructors, so the Copytrait means “this type can be safely memcpy()d”. For types that can be cheaply and infinitely duplicated without (heap) allocation, like u32, copy vs. move isn’t that much of a semantic difference.

              The closest thing to C++’s copy constructor is the Clone trait, whose .clone() method will make a separately-allocated copy of the thing. Clone is never automatically invoked by the compiler, so the difference between moving a String versus copying a String is somefunc(my_string) versus somefunc(my_string.clone()).

              lack of exceptions

              As a Python programmer, I’m pretty happy with Rust’s error-handling, especially post-1.0 when then ? early-return operator was added. I feel it’s a very nice balance between C and Go-style error handling, which is explicit to the point of yelling, and Java and Python-style error handling, which is minimal to the point where it’s hard to say what errors might occur where.

              limited metaprogramming

              It depends how much you care about getting your hands dirty. Rust doesn’t have full-scale template metaprogramming like C++, but the hygenic macro system (while limited) is a good start. If you want to go further, Rust’s build system includes a standard and cross-compilation-friendly system for running tasks before your code is compiled, so you can run your code through cpp or xsltproc or m4 or a custom Python script or whatever before the Rust compiler sees it. Lastly, “nightly” builds of the compiler will load arbitrary plugins (“procedural macros”) which will let you do all the crazy metaprogramming you like. Since this involves tight integration with the compiler’s internals, this is not a stable, supported feature, but nevertheless some high-profile Rust libraries like the Rocket web framework are built on it.

          2. 2

            Linux and DragonFlyBSD both have a simple means to prevent re-parenting past a particular process

            Hmm?? This sounds very interesting! Please tell me more about it.

            upstart apparently used ptrace to track forks

            Oh, this made me realize that I can actually use DTrace to track forks!

            1. 4

              Hmm?? This sounds very interesting! Please tell me more about it.

              In linux: prctl(PR_SET_CHILD_SUBREAPER, 1); In DragonFlyBSD (and apparently FreeBSD too, I see): procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL);

              In both cases this marks the current process as a “reaper” - any child/grandchild process which double-forks or otherwise becomes orphaned will be reparented to this process rather than to init. Dinit uses this already to be able to supervise forking processes, but it still needs to be able to determine the pid (by reading it from a pid file). There’s the possibility though of inserting a per-service supervisor process which can then be used to keep track of all the processes that a particular service generates - although it still doesn’t provide a clean way to terminate them; I think you really do need cgroups or jails for that.

          3. 2

            [Rust’s] designers made the unfortunate choice of having memory allocation failure cause termination – which is perhaps ok for some applications, but not in general for system programs, and certainly not for init

            Or just run under Linux and have random processes killed by the OOM killer and random times because that’s so much better letting a program know the allocation didn’t really succeed twenty minutes ago when it could do something about it.

            1. 2

              Agreed, the OOM killer is totally bonkers, but its existence doesn’t justify stopping a program due to a failed allocation.

              1. 3

                its existence doesn’t justify stopping a program due to a failed allocation.

                Yes, especially since overcommit can be turned off, which should largely (if not always - I’m not sure) prevent the OOM killer from acting.

                1. 1

                  IIRC overcommit is even off by default in Debian.

                2. 1

                  Right. I was saying just let malloc return NULL and let the program deal with it instead of basically lying about whether the allocation succeeded or not. I disable memory overcommit on most of my systems.

                  1. 1

                    For C I totally agree.

                    The Rust equivalent would be:

                    let b = Box::new(...);
                    

                    But Box::new doesn’t return a Result. If allocation fails, the program is terminated.

                    And so far we have only really talked about the heap. As far as I can tell you never know if stack allocation succeeded until you get a crash! Even in C. But I suppose once the stack is hosed, so is your program, which may not be true for the heap.