Deprecating Safe Haskell, or heavily investing in it?

Hi everyone, and happy holidays.

I am looking into whether or not Safe Haskell is still worth maintaining.

Currently there are two sides on which Safe Haskell hurts us:

  1. GHC Development
  2. Library development

For point n°1: You can easily take the measure of what Safe Haskell raises in GHC as of today by visiting the bug tracker¹ and see that for example Unboxed Types cannot be used because their home modules (GHC.Prim and GHC.Types) are marked as Unsafe. Moreover, interactions between GHC2021 and Safe make the latter quite unpleasant to work with².

Regarding point n°2: Safe Haskell is badly integrated within our existing frameworks for API compatibility. Neither the PVP nor the extension’s documentation mention compatibility, or define what stance we should take. Thus we are bound to fight on the letter versus the spirit of the PVP (which is not an unreasonable debate since there is no formalism anywhere). This provokes debates without clear resolution beyond "Friend don’t let friends use {-# Safe #-}"³

Now I am not saying that Safe Haskell does not bring any kind of good idea, far from it.

However there are two things that live inside Safe Haskell that would benefit from being separated:

  1. Strict type-safety

  2. Restricted IO

A lot of the public use-cases of Safe Haskell seem to be on the “Restricted IO”, such as Lambdabot and other code evaluators out there.

It’s a fairly reasonable feature, I’d even say it’s something that we should be publicising more when speaking about GHC’s more advanced features. However, “Strict type-safety” seems to be the root of many problems that we encounter downstream. A lot of it stems from GeneralizedNewtypeDeriving being marked as unsafe (under point 1), which is fair enough, but we’ve had DerivingVia not marked as Safe until May 2021, which reveals a big problem: Options have to be marked as forbidden under Safe to be caught, which leaves a lot of work to the human factor of GHC development.

Now, there are two options (convenient!) that are left to us:

  1. Deprecate Safe Haskell: We remove the Safe mechanism as it exists today, and keep the IO restriction under another name. This will certainly cause much joy amongst maintainers and GHC developers alike. The downside is that we don’t have a mechanism to enforce “Strict type-safety” anymore.

  2. We heavily invest in Safe Haskell: This is the option where we amend the PVP to take changes of Safety annotations into account, invest in workforce to fix the bugs on the GHC side. Which means we also invest in the tools that check for PVP compatibility to check for Safety. This is not the matter of a GSoC, or a 2-days hackathon, and I would certainly have remorse sending students to the salt mines like that.

I do not list the Status Quo as an option because it is terrible and has led us to regularly have complaints from both GHC & Ecosystem libraries maintainers. There can be no half-measures that they usually tend to make us slide back into the status quo.

I have probably missed crucial elements in my post, please feel free to add information or correct me.

So, what do you think?

25 Likes

At this point i’d say deprecate it and go back to the drawing board. It’s both useless, and an inconvenience at this point.

10 Likes

Safe Haskell strikes me as a bit of a failed experiment. There’s a reason why ‘friends don’t let friends use Safe Haskell’. So I’m in favour of deprecation and redesign.

9 Likes

+1 for deprecating it.

Safe Haskell has been an experiment and in the spirit of GHC being fertile ground for experimentation i think it’s very much in our best interests to acknowledge that it was not an especially ergonomic implementation of the core ideas.

we shouldn’t be afraid to reflect upon things that sounded promising initially, but which have ended up falling short of our expectations.

6 Likes

See also the thread: [Haskell-cafe] Safe Haskell?

I personally think safe Haskell could be the thing that sets apart Haskell from other languages. But I indeed think it doesn’t live up to that as it is now.

This confuses me, it seems to me that 2 requires 1 (otherwise I can just unsafeCoerce unrestricted IO) and Safe Haskell doesn’t directly provide 2 AFAIK.

4 Likes

Yes indeed, Safe Haskell provides the foundations to do 2, but we can do it in another way (like with nsjail).

See 6.18. Safe Haskell — Glasgow Haskell Compiler 9.4.4 User's Guide on Restricted IO Monads

Yes indeed, Safe Haskell provides the foundations to do 2, but we can do it in another way (like with nsjail ).

So essentially outside Haskell? If so, I think you could reword the proposal a bit to make that clearer.

It’s not a proposal, it’s a discussion. :slight_smile:
The proposal will appear on the GHC Proposals repository.

The idea that we can’t ever use safe coercions (coerce, DerivingVia, GeneralizedNewtypeDeriving) in Safe code because there could be some old Trustworthy-marked (or otherwise sensitive) module somewhere that fails to include a necessary role annotation is simply obscene.

4 Likes

…but will they still be as joyful when multithreaded GHC is the default, certain unsafe features/extensions are behaving badly as a result, and they’re hunting for those new bugs?

A more-measured process seems the better option here:

  1. Implement all the approved language and implementation proposals which can interact badly with unsafe features or extensions;

  2. Using the experience gained in step 1 and elsewhere, attempt to improve Safe Haskell;

  3. If Safe Haskell cannot be salvaged, use the experience from both steps 1 and 2 to devise a satisfactory replacement for the vast majority of Haskell users.

  4. If that replacement cannot be found (e.g. in a reasonable time period) only then should Safe Haskell be deprecated, leaving us with the joys of hunting for obscure bugs, dealing with fragile tests, etc - fun and games for everyone!

If only someone could figure out a way to reduce (or even vanquish!) the need for all that “unsafeness” - it would reduce (or vanquish) the need for such a process to begin with.

2 Likes

What does the threaded run-time have to do with Safe Haskell? There’s Trustworthy code all over the ecosystem, including base, that causes every bit as much trouble as “unsafe” application code.

Fully in favor of deprecation of Safe Haskell. The fact is that Safe ecosystem failed to materialise. Instead of it there are lies, big lies and Trustworthy slapped over every piece of unsafe abomination you can imagine.

7 Likes

The question is why did a Safe ecosystem fail to materialize, though? It is because of deep and fundamental design flaws, or is it because of a few key fixable hurdles, e.g. poor communication about how it was meant to be used? The package guidelines don’t say a single word about Safe Haskell. It’s hard for me to clearly see how people not using a feature they’ve never been asked to use is a condemnation of the feature. Safe Haskell doesn’t buy you anything unless you have a system that runs untrusted code. Most people don’t do that. So we learn next to nothing from the obvious result: volunteers who have no personal need for safety metadata, and have never been asked for it, don’t maintain it.

I must ask because I still don’t understand - why is coerce unsafe? Since it requires the constructor to be in scope, does it actually let you do anything that you couldn’t do without it? This is essential to the discussion, IMO, of whether Safe Haskell can be redeemed. It’s a huge obstacle right now that so many forms of deriving cannot be done in a Safe module, and personally it is the only reason why so many of my own packages ignore safe haskell.

2 Likes

Safe Haskell does not allow you to run untrusted code: partial functions, error and undefined are “safe”, infinite recursion is “safe”, exhausting system resources and deadlocking is “safe”, and any function which returns IO can “safely” launch missiles. The notion of safety employed by Safe Haskell is very narrow and not helpful in general. In my books this counts as a fundamental design flaw.

4 Likes

@Bodigrim If I take an untrusted module that defines a value of type exerciseSolution :: Seq Text and compile it into a safe program that asserts that the student’s exercise solution is correct, with a timeout to take care of infinite recursion, what could possibly go wrong security-wise?

IO is irrelevant. You would never run IO from an untrusted module.

I personally have never understood what Safe does, and as such would not even know what I’d be missing if it were deprecated. So yeah, unless someone has a good reason to keep it, I’d also vote for deprecation if it hinders GHC developers. :woman_shrugging:

4 Likes

Please correct me if I’m wrong: The demo on the front page of haskell.org utterly depends on Safe Haskell, does it not?

I think section 5 of the Safe Haskell paper gives a pretty good outline of what the imagined use cases originally were. tryhaskell.org, which is still in use today, is one of them. It is aknowledged from the very beginning that untrusted effectful code would need to be defined in a custom limited monad that was then interpreted by trusted code, not in IO directly.

For instance, the untrusted module can allocate a petabyte of memory and crash your system.

I find this quite alarming. Can you offer a demonstration? I would have expected the OOM killer to protect the system and limit the scope of such a crash to only the process. I have certainly written many a memory-hog program by accident, and never experienced a system crash as a result. Perhaps a malicious user could do more damage, though. I’m anxious to learn how.

Fundamentally, running untrusted code is a solved problem: every CI service out there has figured it out. Does not make a sense to reinvent an ivory wheel, which can spin only on roads made of fairy dust.

It could be just enough memory for your program itself, but every other application which tries to allocate will be OOM killed.

2 Likes