Also, if you don't like the OCaml syntax, check out ReasonML, which is an alternative OCaml syntax that Facebook developed several years ago. It has the Algol-style that many modern languages today use.
Also, if you don't like the OCaml syntax, check out ReasonML, which is an alternative OCaml syntax that Facebook developed several years ago. It has the Algol-style that many modern languages today use.
As a Haskell fanboy, this is how I feel about Ocaml and F#.
I think the thing that tickles most people about Rust is the thoroughness of the type inference and type checking, which one generally gets from any ML.
If we have three stages: (1) system languages, (2) higher level languages like Dart, Java, C#, (3) very high level languages like Python and Ruby, then the those among the most popular languages in the ML family (OCaml and F#) comfortably occupy the same level as (2).
Maybe that's not what you're after. OCaml's more rare bytecode implementation (instead of native code) is about on par with Python and SML/NJ is pretty slow as well (although faster SML implementations like MLTon exist).
I don’t think this is that bad for rust though. For (a), it turns out that tail calls can make debugging harder, and they can be incompatible with certain type system changes you might want in an ML, and lots of OCaml (perhaps not a true ML?) in practice uses little explicit recursion and instead first-class functions with names like map and iter rather than recursion. So I don’t really think it’s very important. Rust also has these first-class iterator functions although a bit less of map for containers that can have many things (e.g. vectors). Rust iterations allow for writing code that is more functional when memory/performance are stronger constraints because the pipeline gets composed together instead of producing lots of intermediate objects. For (b) I guess the big difference is that the usual tool in functional programming for manipulating data is creating new copies of immutable data whereas in rust it is controlling ownership and then just updating it (a middle ground may be koka where you write code in the former style but the compiled code inspects recounts to potentially just mutate instead)
Why not? I’ve heard people say this about C too and I never understood it: LLVM and GCC both have optimization passes that perform tail-call elimination.
I think they also make debugging harder, aren’t used much in higher-level code, and aren’t necessary if you have good looping constructs.
Rust's first mechanism for error propagation was the now-deprecated try!() macro, which did basically the same thing.
I guess I would even say it's like... most people just want the banana, but got a gorilla holding the banana and the rest of the jungle as well.
…and if you’re concerned about mutability you can use an immutable first language like Scala or F#.
This part specially about closures: https://blog.janestreet.com/oxidizing-ocaml-ownership/
https://blog.janestreet.com/oxidizing-ocaml-parallelism/ And yes, their changes introduce quite some additional syntax.
Thus, Rust for me serves mostly as a C/C++ alternative. I don't plan to ever write anything in C again, and even if it's not my goto language otherwise, for that I'm thankful.
https://graydon2.dreamwidth.org/307291.html
It seems to me that Rust evolved into a better C++, but it did not start out that way.
But it went a completely different way, ripping out any runtime it had, making reference counting a lot more noisy, etc.
It would be interesting to build a "simpler Rust"
I haven't done this, but probably the surest way to do that would be to use a proof assistant and extract the result to Standard ML or to OCaml. Standard ML has verified implementations, too.
I never felt this was a problem when I did Java though (despite their awkwardness - basically forcing coders to not use checked exceptions.)
Rust's control flow syntax for Results and Options are very similar to this but with an added benefit: you don't have to use the ?-operator.
panics is different, however. They are more akin to the way any Java program will happily OutOfMemoryError or NoClassDefFoundError given circumstances not (always) in your control.
With Rust, the overall situation is a bit strange: as a library author, you are expected to deal with the possibility of panics (which gives you all the headaches associated with dealing with exception safety), but as a user, you are not supposed to rely on them. (I expect that most request handler loops will have catch_unwind handlers, to avoid a faulty request taking down the entire process.)
I don't know Rust, but after searching, it seems that it has a panic facility which seems even more escaping than an exception. Happy to be corrected there.
Throwing an exception implicitly delegates error handling back to the caller, but they are not even notified about it. (talking about OCaml)
Some frameworks catch panics automatically. For example, in the Actix web framework, if you panic in an HTTP response the panic will be catchee, so it won't bring the whole server down.
Also, by default a panic will terminate only the current thread, which is a major footgun: you now need to reason about what you program will do after some bug or unforseen circunstance happened somewhere in the code and made you program misbehave. Which leads to slightly insane things like lock poisoning.
It's more sane to compile with panic=abort, but that's not the default and it means that on panic you won't release resources (which aren't just allocated memory to be clear)
And you're working with multiple definitions of "safety" here, and Rust sorta conflates them all via borrow checker, but the one people are usually most concerned with is memory safety which is not a concern for a garbage collected language.
I do seem to recall that StandardML did not have exceptions though. And I always felt that SML was the better language.
OCaml adding OO classes and exceptions and other 90s trends that actually have ended up not aging well...
> I have been writing a compiled, concurrent, safe, systems programming language for the past four and a half years.
Safety was always part of it.
I guess I should have been more specific. If that's what we mean by safe, then OCaml is safe as well.
Anyways, I followed it at the time. The borrow checker came later.
Exceptions look remarkably wrong headed to me in retrospect. Allowing the caller to change the error handling contract and flow control.
In any case OCaml has memory safety, sort of (it has data races but as described in the paper "data races bounded in time and space", data races in OCaml doesn't lead to unrestricted UB like in Rust, C, C++, and most other languages actually) (unless you opt into OCaml's unsafe constructs like Obj.magic, which is like Rust's unsafe without using unsafe {} block), because it has a GC
So when you talk about safety in the context of exceptions, you probably mean exception safety rather than memory safety. Exception safety basically means you code works even if an exception is raised. Which is really hard to assure since as you said, exceptions are everywhere
But Rust suffer from this same problem! In Rust this is called panic safety and mitigating it has taken a great deal of complexity, with things like lock poisoning, which adds overhead but limit the scope of panics in multithreaded programs, and the UnwindSafe trait, which is probably a good attempt but is ignored in most of the ecosystem. Many people think such measures are inadequate and insufficient and prefer to run programs with panic=abort, which means to just terminate the program when there is any panic. (many C++ projects disable exceptions in the same way for example)
Which is kind of unfortunate because now there are many Rust programs that are only correct if you run with panic=abort, and will break if you enable stack unwinding (which is the default)
For reference, this is the type that is returned from catch_unwind: https://doc.rust-lang.org/std/thread/type.Result.html
But I completely agree that Rust has exceptions. They are even used in the toolchain implementation for non-local control flow.
This seems like something that should be allocated at program startup, just like other things like the program's environment (I think it's copied to Rust's own data structures at startup to avoid using the non-threadsafe C API), and other things allocated at startup
.. but of course, not at Linux, such error allocation would be unneeded there..
.. except of you disable overcommit, which can and do happen, so in the general case you don't know if this error object can ever appear
It should be possible to add a third arm to that Result type, returning some &'static reference, but I'm not sure how to do it in a backwards-compatibile way.
static ALLOCATION_ERROR: Box<dyn Error> = (something);
and then use this variable whenever there is an allocation error. you might need unsafe { } but that's okay, the stdlib is full of unsafe
use something like `Maybe` for the result - clumsy.
return (for example) 0, which is what most theorem provers (like Coq or Lean and dependently typed languages like Idris) do, that need their functions to be total
or throw an exception.
(C's solution of declari g it undefined behavior is missing).
Now, with OCaml's effect systém, there also is no need to use exceptions for control flow - which you should have never done anyway.