Hacker News new | ask | show | jobs
by fghgfdfg 3513 days ago
I completely disagree with those statements.

Rust can give stronger static guarantees in simpler instances. When it comes to more complex cases, neither language can guarantee much. Ada however has better mechanisms for handling them safely. For example, you can specify a particular allocator for individual types rather than relying on each allocation to use the custom allocator.

If you're outside the realm of the Rust borrow checker and want to use an arena to keep the memory side of things simple and safe, it's much easier to do properly in Ada.

Ada is also significantly safer than C. Between the much stronger type system, memory safety facilities, bounds checking, non-nullable pointer types, generics, contracts etc. it's clearly a much safer option than C.

2 comments

> For example, you can specify a particular allocator for individual types rather than relying on each allocation to use the custom allocator.

Having a 1-to-1 correspondence between types and allocators screams of lack of separation of concerns. In any case...

> If you're outside the realm of the Rust borrow checker and want to use an arena to keep the memory side of things simple and safe, it's much easier to do properly in Ada.

Arenas can be given safe interfaces in Rust just fine: https://crates.io/search?q=arena

> memory safety facilities

The article literally says: “Tasks are awesome, but sometimes they're not quite what you want --- particularly as there's no protection against two tasks modifying the same variable at the same time.”

So much for memory safety.

> bounds checking

Implementable as a library feature. For example, in Standard ML:

    signature BOUNDS =
    sig
      val minBound : int
      val maxBound : int
    end
    
    (* INTEGER and Overflow are in the Basis Library *)
    
    functor BoundsChecked (include BOUNDS) :> INTEGER =
    struct
      type int = int
      fun toInt x = x
      fun fromInt x =
        if x < minBound orelse x > maxBound
        then raise Overflow else x
      val op+ = fromInt o op+
      val op- = fromInt o op-
      val op* = fromInt o op*
      (* rest of implementation... *)
    end
There is no need to bloat the core language.

> non-nullable pointer types

In an unusable way.

> generics

Parametric polymorphism buys you non-repetition, not safety.

> contracts

Contracts don't buy you safety. Your code will just crash at more predictable places when a contract assertion fails. But it will still crash. Hard.

Static analysis buys you safety.

> Having a 1-to-1 correspondence between types and allocators screams of lack of separation of concerns. In any case...

It's a 1-to-1 correspondence between an access type (aka pointer) and an allocator. There isn't an issue with separation of concerns.

> Arenas can be given safe interfaces in Rust just fine

I never said they couldn't. I said, they require the arena allocator to be used explicitly for each and every allocation. So if you screw that up, it's a problem.

> The article literally says: “Tasks are awesome, but sometimes they're not quite what you want --- particularly as there's no protection against two tasks modifying the same variable at the same time.”

And if you bothered to read another few sentences, you would see the article mention controlled types. These are types that exist to be task-safe. It's entirely safe.

> <bounds checking> Implementable as a library feature.

Which makes it more difficult or impossible (depending on other language features) to detect such errors at compile time. It's also much harder or impossible to optimize away the checks when possible. And finally being a separate library means it's not part of the core language. You might call that bloat, I call it good design that not only requires safety but fosters an environment that encourages safety. You can have all the best most safe features in the world, but if they are hidden off in a library then programmers must explicitly reach for them and by default start off playing around in unsafe ways. It becomes a cultural thing.

> (non-nullable pointer types)In an unusable way.

What? Care to expand? I've used them before, so they are usable in some sense at least...

> Parametric polymorphism buys you non-repetition, not safety.

Non-repetition buys you a single place to screw up, rather than many.

> Contracts don't buy you safety. Your code will just crash at more predictable places when a contract assertion fails. But it will still crash. Hard. Static analysis buys you safety.

Where possible Ada contracts are checked statically. Contracts that are checked at run-time do buy you safety too, though. Not in a given build, but unless you are in the habit of shipping as soon as you get a successful compile, having crashes occur as soon as there is a problem is a wonderful way of being able to quickly identify not only that a problem exists, but what the problem is. In terms of actual software development, it very much helps with producing correct software.

It's relatively easy to create a type which can't be created at all by functions outside the module it's defined in - add any private member. Then that module is in control of how it's created, and can set any restriction you like. This pattern is regularly used in Rust to ensure that types are always initialised correctly.
Sure, you can do things like that too in Ada. To some degree that might make sense sometimes, but having the option of an equally safe type that's used in a "normal" fashion, rather than something that has to go through the proper library interface has some advantages as well.
It's relatively uncommon in Rust in general to create types without going through their constructors.