Rust prevents a few types of unsafety, mostly relating to memory and concurrency. There are many more on which Rust does not do particularly well though. Specifically, Rust allows arbitrary side effects from basically any part of the code. You can always write to stdout or a logfile, or open up new sockets, or delete files from the filesystem and there will be nothing in the type signature to even warn you about this.
>You can always write to stdout or a logfile, or open up new sockets, or delete files from the filesystem
Haskell has a separate problem here: all of these can fail, and there's nothing in the type system to alert you of this (in the standard library), such failures just mindlessly throw exceptions like some Java monstrosity. In Rust, on the other hand, all such functions return something like Either Result Error, forcing the caller to check (and ideally handle) any errors. Not to mention async exceptions in Haskell, which can happen anywhere, and the fact that every value is really of type value|undefined, due to laziness. It's practically impossible to cleanly formally reason about code in Haskell due to the fact that anywhere could be interrupted by an async exception.
When even C++ is considering trying to remove exceptions from the standard library, Haskell's love for untyped exceptions everywhere is seriously behind the times for a language that prides itself on correctness.
I used to bash Haskell exceptions but my views changed recently after programming in Haskell for a while.
> all of these can fail, and there's nothing in the type system to alert you of this (in the standard library), such failures just mindlessly throw exceptions like some Java monstrosity
There's `MonadThrow` in Control.Monad.Catch which hints that the monad in question can throw exceptions. Admittedly, partial functions like `undefined` and `error` are still usable...
> Not to mention async exceptions in Haskell, which can happen anywhere, [...] anywhere could be interrupted by an async exception
... and they can throw exceptions everywhere, just like asynchronous exceptions, but it's actually a strength! Haskell enforces a clean separation between impure code (typically in a monad) and pure code. You can only catch exceptions in the IO monad, which often lies outside of the core logic. Due to this unique strength, Haskell is one of the very few languages that can safely terminate running threads.
Impure code can become harder to write because of exceptions, but since you don't write everything in the IO monad, the problem is largely mitigated. Yes, exceptions are hard to get right, and that's exactly why other languages are trying to get rid of, but Haskell makes it quite tractable, (though still quite annoying). Rust used more Maybes and Eithers in the IO monad (to borrow jargons from Haskell), but it's also got panic, which is the actual Haskell exception counterpart.
> and the fact that every value is really of type value|undefined, due to laziness
To be pedantic, Haskell has levity polymorphism, which gives you unlifted datatypes, like in OCaml and Idris. Even older Haskell has unboxed datatypes that are not lifted.
> ...Haskell's love for untyped exceptions everywhere...
> > ...Haskell's love for untyped exceptions everywhere...
> Nope, Haskell's exceptions are typed.
logicchains means that the exceptions that a function can throw are not noted in its type (and as a massive Haskell fan I agree with him/her that that is very annoying).
>write to stdout or a logfile, or open up new sockets, or delete files from the filesystem
Do those things come under the category of unsafe? Why would we be programming if it weren't to do those kinds of things? If I buy a shovel, at some point I'll probably want to dig a hole with it.
Absolutely. If you watch the youtube video linked in this thread, SPJ makes that very comment, that a program with zero effects is useless.
However, the goal of haskell (and indeed any language trying to be safe in the way SPJ means) is to be able to have most code be safe/effect-free, and then to have effects be very carefully controlled and limited in their use. Things like the IO monad mean many parts of haskell code can't do IO in fact.
We obviously do want some sort of effect in the end, but the idea is it's safer to contain those effects in very limited places, and not allow effects to happen literally anywhere at any time.
Note, unsafe in the SPJ video was specifically about effects, while "unsafe" in rust terminology is mostly about memory safety, so those two terms really aren't the same word, and to be honest that can make communication less clear. I don't know what "category of unsafe" meant in your comment really.
If you'd watched the video from the grandparent comment, you'd have seen that those kind of uncontrolled side effects are exactly what Simon Peyton Jones was talking about in the video when talking about "safe" versus "unsafe" languages.
But in any case, yes those things I mentioned can fall under the category of "unsafe". In the category of memory safety, you want to be sure (with compiler guarantees!) that no thread will secretly free some allocated memory while you are still using it. This is something the borrow checker can give you in rust. There are no builtin guarantees in Rust that this fancy new library you imported won't do `DROP TABLE users`, or open a socket to https://haxorz.com and pass on all the environment variables. There are other languages in which you can be sure that a function cannot open any sockets unless it specifically has "can open sockets" encoded in its type.
> There are no builtin guarantees in Rust that this fancy new library you imported won't do `DROP TABLE users`, or open a socket to https://haxorz.com and pass on all the environment variables.
Indeed, and I think it was a mistake by the parent poster to cast it as some sort of security thing. It very much isn't.[0]
Anyway, controlling side effects is really about avoiding accidental/unintentional side effects, i.e. mutating the world[1] by accident. Of course if everything is in IO, you only get "can do anything to the world" and "can't change the world at all", so Haskellers are usually interested in more fine-grained separation of effects than just pure/impure.
Of course, you are also trusting that code you're using doesn't do crazy unsafePerformIO, etc. stuff, but at least you can grep the code for that :). And sometimes unsafePerformIO can be a good thing to do 'magical' things which are referentially transparent but require mutation for asymptotics or performance generally.
[0] Safe Haskell is more about that kind of thing, but AFAIUI it isn't really in much use and never really took off. IIRC, it might even be slated for removal?
[1] Which is the ultimate shared mutable state, after all.
Right, my "in practice" was hedging for the existence of SafeHaskell, which does rely on the types and is "built in" to GHC, but as you say isn't really used by the community.
I get that. (Another way to do it at run time is capabilities). What I don't like is calling this "unsafe". We know that use after free is never something anyone intended. We don't know that about opening a socket. If we take the attitude that any effect is unsafe then soon we will feel we have to control every one of them. If I have to control everything someone else does then I might as well do it myself (i.e. you eventually start to leak the implementation details and lose flexibility). Call it contracts or capabilities or something but not unsafe.
Use-after-free is bad. "Unsafe" isn't the same as bad; unsafe means "could be bad". The reason that is a useful definition for unsafe, is that it allows us to define safe as "definitely not bad".
In Haskell, a function like 'Int -> String' is safe (definitely not bad). A function like 'Int -> IO String' is unsafe (it might be bad; we hope not). If it were possible to specify "bad" via the type system (like the type of use-after-free) then we would want that to be a type error (like it is in Rust).
I don't know. Int -> String could be bad if the algorithm that calculates the value of the string is bad. But anyway I suppose I don't like changing the use of language where "unknown" now becomes "unsafe". Unsafe to me means that I know something about it. If I know nothing about it then it is neither safe nor unsafe. It's just unknown. Why not call it that? Otherwise we have two words for the same thing and have made our language more imprecise. (Alternatively if we take the attitude that something unknown could be good then we could argue that we call it safe).
The point is not that you shouldn't do these things. The point is that Rust does not provide any tools to help you do it safely. Mutation is also a necessary thing in programming, which can be unsafe if done incorrectly. Rust has many built in rules for keeping mutation safe and requires labelling functions that mutate. For side effects, there is not much of a safety net in rust.
The part you missed is "always". The unsafe part is being able to do this from any function, instead of only from functions explicitly marked as being able to perform effects.
The basic idea isn't that you want to avoid such activities, but that you want to know which functions do it, so that it is easier to reason about your program.
I think SPJ is using a slightly different definition of safe than Rust does! But Rust should definitely be a bit up his safeness scale, but not all the way, doesn't fully control all effects.