Ask HN: Memory-safe low level languages?

1 day ago

I am looking for memory-safe languages that can be used for systems/graphics programming. I love Rust, but it often feels like too massive a language with too much stuff going on. Is there a language like C, which is simpler (obviously without all the UB and other problems)?

This is an especially hard ask, given how useful the FP-like features of Rust are, and I find it almost impossible to live without them. Basically, I am looking for a middle ground between C/Zig and Rust.

One such language I have found is Austral[0]. What other such languages are there?

[0] https://austral-lang.org/

SPARK (Ada) is about as memory-safe as it gets.

https://github.com/AdaCore/spark2014

There have several attempts at cleaning up C without giving up too much of its simplicity; from what I can see, Zig is the only one even close to reaching critical mass.

A programming language is always going to make some kind of compromise; better at some things, worse at others.

Simplicity/power and safety pull the design in different directions.

  • Zig hits on a lot of Zen of Python.

    > Beautiful is better than ugly.

    > Explicit is better than implicit.

    > Simple is better than complex.

    > Readability counts.

    What really sets Zig apart from the usual suspects (C, C++, Rust) is that it has first class compile-time reflection capabilities built into the language (comptime, @field, @typeInfo, etc.) rather than bolted on as macros or templates.

  • I don't mind the language having substantially worse "something" as long as it can be a smaller alternative for Rust, for the lack of a better word. Of course, there always needs to be some compromise. I don't mind that. I just have two requirements, and am curious to see how people have tackled that problem.

(link from the description, clickable)

https://austral-lang.org/

> Austral is a new systems programming language. It uses linear types to provide memory safety and capability-secure code, and is designed to be simple enough to be understood by a single person, with a focus on readability, maintainbility, and modularity.

  • Austral GitHub has not received any significant updates for the last two years so I suppose the language development stopped.

Pascal (e.g. FreePascal).

Modula-2 (e.g. GCC).

Oberon (e.g. Oberon, Oberon 2, Oberon-07, Oberon+.)

V [0] aims for something along those lines, but I've had a few issues with the safety aspects of the language. Breaking through the checker isn't that difficult.

I absolutely adore Ada [1], but the Pascal-syntax isn't for everyone.

I haven't used it yet, but have been hearing rumblings about Odin [2] a fair bit in these kinds of discussions. I tend to avoid too many symbol-heavy langs, but it's probably still less than Rust (I use a screenreader half the time).

[0] https://vlang.io/

[1] https://ada-lang.io/

[2] https://odin-lang.org/

  • I just so happened to use Odin a bit this week, and I hit several different problems: two compiler bugs and a stdlib bug. I'm not having a go at the Odin people, just pointing out it's very much an "in development" language with some fairly sharp edges.

  • I see so much controversy every time V comes up; can someone explain why, without devolving into name calling and personal attacks? What specific design choices or implementation details are contentious, and what legitimate concerns exist beyond interpersonal conflicts?

    • Having only watched the discussion: The main problem seems to be grossly inflated claims about its capabilities. As in, the docs say it already does X Y and Z, and when you try them they plainly don't work. And then the creator starts with the personal attacks when you point this out.

    • When it launched it proudly claimed to do a lot of things, some of which most people would consider still open research questions. Unsurprisingly, it didn't actually do these things or have a concrete plan of achieving them, and they didn't handle people pointing that out very well. Some other language developers were unhappy about the support V gathered based on these claims vs languages that were further along but honest about what they actually had.

      (I have no idea what the current state is)

      1 reply →

    • V... Overpromised, and underdelivered, on what it could do. Promised complete Type Safety, before the type checker was even implemented, for example.

      As for concerns... The main developer is a concern. Hard to trust them to support the language well, with some of the... Well, tantrums. This isn't aimed at a personal attack. But it is very hard to describe their responses in another manner.

      This [0] thread on HN covers some of all of the above.

      But, probably also important to point out that V and its drama have had dang threaten to ban the topic altogether [1]. There's a lot of drama.

      [0] https://news.ycombinator.com/item?id=37335249

      3 replies →

  • V looks exactly like one of the languages I was talking about. Some controversy about the project, it seems like, but very cool nonetheless. Even if it doesn't actually work like described, the description seems quite interesting.

    > But I've had a few issues with the safety aspects of the language. Breaking through the checker isn't that difficult.

    What do you mean? Can you "break through the checker" outside of unsafe blocks?

https://dlang.org/articles/safed.html check out the safed subset of the D language

  • Unfortunately, it seems to use a GC for the safety, which makes it unsuitable for a variety of tasks in the systems programming domain. Seems to me like an alternative to Go more than Rust or C or Zig

    • D has a “BetterC” subset that depends only on a C runtime. Presumably there is some overlap with the “SafeD” subset.

      Curious if there are any D users here who try to stay within that intersection?

    • On the contrary GC is very suitable to systems programming.

      The bigger problem is guaranteeing concurrency safety, which you can only provide with nonblocking IO. Everybody loves blocks though, so that's far away.

    • >it seems to use a GC for the safety, which makes it unsuitable for a variety of tasks in the systems programming domain.

      That depends. It is really a Hard NO for GC?

      Crystal is low level enough with a GC and people have written a complete OS with it. On my mobile now so I can't provide link. But it was featured on HN a while ago.

I also love rust and we use it heavily at our startup, but I agree with you and wish there were a mainstream alternative that kept much of the type system, pervasive expressions, and pattern matching while being smaller. I’d accept “very fast” even if it’s not as fast as rust.

One project I’ve seen that I don’t think is particularly active but that I really like the ideas behind is borgo. It compiles to go (thus GC) but is decidedly more rustacean.

Check it out. I hope someone makes something like this wide spread.

https://borgo-lang.github.io/

PS. I have no affiliation with borgo, just an online fan.

F-star, which was used to build a verified TLS implementation. https://fstar-lang.org/ Though I guess that's actually on the far side of Rust relative to what you're looking for.

  • The full language is indeed even further ahead of Rust on the spectrum I am looking at, but this seems like a cool effort. I love proof-based languages and dependent types, so this is an absolute win.

    What intrigues me quite a bit relative to the main topic of the HN thread is Low[0]

    > Low is not only a language subset, but also a set of F* libraries that model a curated set of C features: the C memory model, stack- and heap-allocated arrays, machine integers, C string literals, and a few system-level functions from the C standard library. Writing in Low, the programmer enjoys the full power of F for proofs and specifications. At compile-time, proofs are erased, leaving only the low-level code to be compiled to C. In short: the code is low-level, but the verification is not.

    [0] https://fstarlang.github.io/lowstar/html/Introduction.html#t...

Vale is interesting, https://vale.dev/

For memory safety compared with Rust it does not use a borrow checker for the price of more checks and extra memory usage at runtime while avoiding GC or even reference counting.

I think this is very interesting trade off. Plus there are plans to implement few interesting ideas that even Rust type system cannot implement while keeping the language simple.

So one kinda cool direction is what they do for the sel4 prject. They have a sequence of high to low level impls and prove correctness wrt spec of the high level etc and prove that each lowering is an implementation of a refinement of the higher level implementation

If you love Rust -- use Rust. Trying to find a language that's just right for you risks it being just that: right for you and few others.

Just remember that even if writing memory-safe programs is your goal, using a memory-safe language is just a means to that goal, and even Rust isn't really memory-safe. Many Rust programs try to achieve memory safety while using a non-memory-safe language (be it C or unsafe Rust), and there's an entire spectrum of combining language features that offer sound guarantees with careful analysis and testing of unsafe code to achieve a safe program. On that spectrum, Zig is much closer to Rust than to C, even though it offers fewer guarantees than Rust (but more than C).

  • I love Rust, and will continue to use it. But sometimes it feels like "too much". If you have programmed in Rust, you know what I mean. I want to use and experience a language that is to Rust almost like what C is to C++.

    This is primarily an educational exercise to see how people find compromises that work for them, and languages in the same space as Rust using alternative strategies.

    • I've programmed in Rust extensively, and I'm on the Rust language team. I don't quite know what you mean, and I would genuinely like to. If Rust feels like "too much", I'd be interested in knowing what makes it feel that way and how we might be able to improve Rust to avoid that feeling.

      Is this something you experience when writing your code, or is this something you experience when reading other people's code?

      If it's the former, I'd really love to hear more about those experiences.

      If it's the latter, are there particular features that crop up that make code feel like too much?

      (To be clear, Rust isn't perfect for everyone, despite our best efforts. And if you want to work with another language, you should! I'm not looking to defend it; your experiences are valid. We'd love to make Rust better, so I didn't want to miss the opportunity to ask, because we so rarely hear from people in the intersection of "I love Rust" and "Rust is too much".)

      7 replies →

    • If it's for educational purposes and you want to explore various tradeoffs, then you shouldn't necessarily restrict yourself to languages that make similar tradeoffs regarding safety guarantees in the language as Rust. Again, the goal of writing a memory-safe program is understandable, but there's more than one way to achieve that goal when it comes to language guarantees. That doesn't only apply to languages that offer fewer guarantees than Rust, but also to languages that are possibly less low-level (e.g. OCaml, Nim).

      But even for educational purposes, using a language with a poor selection of libraries is likely to lead to a bad experience if what you want to produce is working, non-trivial software. Every project includes some "boring" aspects -- such as parsing configuration and data files -- that you won't necessarily enjoy writing from scratch. The overall programming experience is shaped by much more than the design of the language alone.

    • You might like Ada as a few people have said. Rust seems kind of niche oriented to me, aimed at programs that for whatever reason don't want to use GC, but ALSO want to use dynamic memory allocation a lot. Ada isn't that great at memory management and mostly aims at embedded programs with fairly simple (maybe just one-time static) memory allocation. In other regards though, it's safer and in some ways simpler than Rust, from what I can tell.

    • I do not see any serious contender to C. And considering that most people developing alternative languages that aim to replace C do not seem to have a good understanding what makes a good system programming language, I also do not see this changing soon. Tooling for memory safety will improve and I expect we will also have something complete in ISO C at some point. But already today, one does not have to write modern C as your parents did, e.g. there is no need to do unsafe pointer arithmetic and many other unsafe features can simply be avoided. Signed integer overflow can be checked at run-time. Only temporal memory safety is missing a good solution that ensures safety, but I do not find this is to be a major problem in my projects (with some discipline about pointer ownership)

      2 replies →

    • Rust can feel like "too much" at times. It's a very feature rich language. But that doesn't mean you have to use every feature. With all feature-rich languages I think that's good advice, since code that does use every single feature often ends up being an unreadable mess. Each feature is there for a certain use case, not for every use case.

  • > even Rust isn't really memory-safe.

    [Heavy citation needed]

    Rust isn't memory safe if and only if:

    - You messed up writing safe to unsafe interface (and forgot to sanitize your binary afterwards).

    - You tripped one of the few unsound bugs in compiler, which either require washing pointers via allocators or triggering some tangle of traits that runs into compiler bugs

    - Somewhere in dependecy tree someone did the one of other things.

    • > You messed up writing safe to unsafe interface (and forgot to sanitize your binary afterwards).

      That is the definition of a language not being memory-safe. Memory-safety in the language means a guarantee that the resulting program is memory safe, and that you could not mess it up even if you tried.

      Taking that to the extreme, it's like saying that a C program isn't memory safe only if you mess up and have UB in your program, something that C program must not have. But C is not a memory safe language precisely because the language doesn't guarantee that. My point is that there's a spectrum here, the goal isn't memory-safety in the language but in the resulting program, and that is usually achieved by some combination of sound guarantees in the language and some care in the code. Of course, languages differ in that balance.

      7 replies →

    • Welcome to C++

      C++ would be great if people didn't use C style de-reference or arrays, used smart pointers, and avoided "turing complete" template bullshit. It ironically would be almost as memory safe as Rust.

      On the contrary, you can also make C very memory safe by avoiding a few patterns.

  • > even Rust isn't really memory-safe.

    Yeah, and this points at a deeper issue: the concept of a language being either (binary) memory safe or not does not really make sense. Memory safety is a spectrum and most GC'd languages are safer than Rust (but you won't see a lot of "memory safety" proponents acknowledge this).

    Also, there are mitigations that can make "unsafe" languages as secure as something like Rust, but those are deliberately overlooked and hence languages like Zig are constantly bashed using security as the reason.

What I dream about is a C with less UB, range based loops, array slicing (supporting negative slicing too), safer and easier string functions (because my pet project is a compiler so gotta write a scanner), pattern matching switch, and maybe a few other stuffs.

Oh maybe less preprocessing black magics :/

  • UB issues in C is way overblown. Just don't do dumb shit with null pointers and use const where appropriate that eliminates most of it.

    As for other stuff, I was working on a LLVM extension a while back to put a lot of Python-isms into C, like the array slicing, print statement, f strings, and decorators.

    Generally though, I found that writing your own macros is actually easy, and you just have to type stuff out a bit more. Last project I worked on in C I had an include file that had all the macros and the defined functions to go with the macros. The print statement honestly takes up most of the file, cause its a combination of vararg in the macro with _Generic selector with functions defined on how to print each type of variable. But the rest of the stuff like array slicing is just a macro that calls a function that takes the slice arguments as a string. Doesn't look as nice as actual array slicing, but it works and easy to write.

Personally, I don't know what you mean about Rust being too massive. One thing I am wary of is using a truly massive language like C++ on a multi-programmer project without consensus on which features to use and how to use them. Maybe you have in mind something like that?

If you want the simplicity of C with more safety, maybe tooling like Frama-C, a MISRA C conformance checker, or just aggressive use of static and dynamic analysis tools like ASAN and UBSAN. You can also disable certain optimizations (e.g., strict aliasing) to steer away from some of the major pitfalls of UB.

See https://wiki.alopex.li/SurveyOfSystemLanguages2024

and the related discussion https://lobste.rs/s/c3dbkh

Serious total MSLs that have a defined memory model to allow "low level" operations seem to be scarce if not any. Hence I do not think there is any single one that comes between C/Zig and Rust.

I would have said https://www.hylo-lang.org/ but, personal opinion, seems like there is too much going on as well. See also https://vale.dev/

P.S Mind mentioning what FP-like features are in Rust? (Genuinely have not looked into Rust that much but I am interested).

GCC nowadays offers Modula-2, Ada and D in the box.

Then you have FreePascal, as FOSS ObjectPascal dialect.

On Apple's turf, Swift naturally, given its bindings for all Metal anything frameworks.

  • Modula 2 seems to not have libraries for things we might need these days, such as file format support for reading zip and read/write of png, gif, etc. Also TCP/IP comms?

    • There are libraries around,

      https://github.com/nbrk/m2-raylib

      Naturally given the way it has been largely ignored in the last 30 years, the choice is limited, yet GCC developers considered it had a community big enough to integrate GNU Modula-2 as standard frontend on GCC 14.

Side question and possibly off topic, but is there a formal definition to the term "memory safe"? It seems to mean different things to different people and I'm unsure if I'm just out of the loop and there is an actual definition.

  • A memory safe language is one for which (a) there is a subset of programs which can be statically proven to not perform unsafe memory operations at runtime and (b) no programs outside of this subset will be accepted. The set of operations that are considered to be unsafe can vary, but always includes writes to unowned memory, and often includes reads from unowned and/or uninitialized memory.

You may want to look at Lua[0]. It's often used as an embedded scripting language in larger projects (and games), has good performance, is memory safe, and is extensible in the same manner as Python (write your performance bottleneck in C/C++).

I don't remember specifics, but there are some odd footguns to look out for.

[0] https://www.lua.org/

Have you tried "modern" Fortran? I've seen horrible "old" Fortran programs, but once you add modules it get's much better.

  • I was playing around with Fortran (modern-ish) recently was pretty impressed with the entire ecosystem. `fpm` is really really nice to work, pretty decent LSP server (fortls) as well as good enough documentation.

    I am not sure however I like the verbosity of it where if you are using 'raw' editors without snippet support, it becomes a chore very soon.

    All in all it is nice to use and play around with.

Have you considered trying one of the older Rust versions (e.g. 1.0.0 or a bit after that) without all the new shiny features?

If performance is not ultra critical, I'd use Go. It's simple and a small-ish language. Otherwise there's not many options. Not to belittle your question, because it's a good one, but it's a little like asking if there's a simpler aircraft. There can be, but there's a certain amount of required machinery to keep it in the air. Rust's memory safety rules are as simple as we can get it for now. Maybe in a few years it'll be different!

i was actually looking for an answer to a similiar question, so this thread is very much useful! as of now, i looked somewhat at odin

Very old school:

ASP[0] was old way to do character animations for unix finger command .plan.

Recall HN post using python to do the same (handled animation independent of serial terminal speed).

Related HN post "John Carmack's .plan file" [3]

hascii mation [1]; Use standard graphics techniques and run through [2] or .plan with unscii[4]

"sheltered code" via mindcraft : https://youtu.be/7sNge0Ywz-M

------

ascii animation tutorial : https://www.youtube.com/watch?v=o5v-NS9o4yc

[0] :Ah, ASP link has been merged with /dev/null. :(

     ASP and related things for animated .plan : https://superuser.com/questions/253308/scrolling-plan-file

[1] : http://viznut.fi/unscii/

C# has been used as the primary language in various hobby/research kernels.

  • > C# has been used as the primary language in various hobby/research kernels.

    That's quite the oversimplification...

    C# is not a systems-programming language: it's an application-programming language that is heavily dependent on the CLR runtime environment. While those research-kernels certainly do bring a-kind-of-CLR into the kernel it's far from being like the CLR in .NET; but they don't use C# - at least, not the same C# you use in Visual Studio: those research kernels: Singularity, Midori and Verve - used not only the Sing# and Spec# extensions to C#, they had their own compiler (Bartok) which itself enabled other language-extensions.

    That said, those extensions are fascinating reads:

    Sing# concerns message-passing ( https://www.microsoft.com/en-us/research/wp-content/uploads/... )

    Spec# added Ada-style (i.e. compiler-enforced) invariants and pre/post-conditions (this was the basis for the Code Contracts feature which was annoyingly/tragically killed-off during the .NET Core reboot in 2016); see https://www.microsoft.com/en-us/research/project/spec/

    Bartok: https://www.microsoft.com/en-us/research/wp-content/uploads/...

    • > C# is not a systems-programming language

      Incorrect, the main restriction are the targets supported by CoreCLR/NativeAOT/Mono(to an extent).

      Or, at least, if you pick all memory-safe languages with GC, C# offers the most when it comes to low-level access.