Can all typechecking occurrences of `coerce` safely be replaced with `unsafeCoerce`?

This should be safe.

A valid coerce @a @b can always be replaced with a valid unsafeCoerce @a @b. Why? Because, at the Core level, they are the same function, coerce (which just returns its input, like id). The thing is that coerce takes, as argument, a proof that the two things being coerced have the same representation. With normal coerce, this proof is an actual proof, but with unsafeCoerce, this proof is just a token that says "trust me". This proof is passed as a type argument, and so, by type erasure, has no effect on the behavior of the program. Thus unsafeCoerce and coerce are equivalent whenever both are possible.

Now, this isn't the end of the story for Set, because coerce doesn't work on Set. Why? Let's peek at its definition.

data Set a = Bin !Size !a !(Set a) !(Set a) | Tip

From this definition, we see that a does not appear inside any type equalities etc. This means that we have congruence of representational equality at Set: if a ~#R b (if a has the same representation as b~#R is unboxed Coercible), then Set a ~#R Set b. So, from the definition of Set alone, coerce should work on Set, and thus your unsafeCoerce should be safe. The containers library has to use a specific

type role Set nominal

to hide this fact from the world, artificially disabling coerce. You can never disable unsafeCoerce, though, and, reiterating, unsafeCoerce (in this context) is safe.

(Do be careful that the unsafeCoerce and the coerce have the same type! See @dfeuer's answer for an example of a situation where "overzealous" type inference bends everything out of shape. )


Yes, this should be safe in typical realistic circumstances. However, it is possible to come up with contrived examples where it's not. Here's one that uses defaulting. I imagine it might be possible to use overlapping instances or other wacky features to do something similar, but I don't know.

{-# language GADTs, TypeOperators, ExistentialQuantification #-}

import Data.Coerce
import Unsafe.Coerce
import Data.Type.Equality

data Buh a = Buh (a :~: Rational) a

data Bah = forall a. Bah (a :~: Rational) a

instance Show Bah where
  show (Bah Refl x) = show x

goo :: Rational -> Bah
goo x = case coerce p of
          Buh pf m ->
            let _q = truncate m
            in Bah pf 12
  where
    p = Buh Refl x

If you call goo, everything will be fine. If you replace coerce with unsafeCoerce, calling goo will segfault or do something else bad.