__readFile vs. builtins.readFile

In some advanced nix code (haskell.nix) I spotted a use of __readFile. What is the difference between that and the (documented) builtins.readFile?

Looking at this nix code, it seems that some builtins are registered in the global environment with __ in the name, but this is stripped before adding them to the builtins set, so there is no difference:

Value * EvalState::addPrimOp(PrimOp && primOp)
{
    /* Hack to make constants lazy: turn them into a application of
       the primop to a dummy value. */
    if (primOp.arity == 0) {
        primOp.arity = 1;
        auto vPrimOp = allocValue();
        vPrimOp->type = tPrimOp;
        vPrimOp->primOp = new PrimOp(std::move(primOp));
        Value v;
        mkApp(v, *vPrimOp, *vPrimOp);
        return addConstant(primOp.name, v);
    }

    Symbol envName = primOp.name;
    if (hasPrefix(primOp.name, "__"))
        primOp.name = symbols.create(std::string(primOp.name, 2));

    Value * v = allocValue();
    v->type = tPrimOp;
    v->primOp = new PrimOp(std::move(primOp));
    staticBaseEnv.vars[envName] = baseEnvDispl;
    baseEnv.values[baseEnvDispl++] = v;
    baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
    return v;
}
1 Like

That is exaclty the answer. To be complete, builtins (aka. primops) are either considered toplevel and directly available in the global environment, or hidden away with these two underscores. But all builtins are available unprefixed under the builtins variable.

That being said, the prefixed version is seldom used. It may be worth to switch to the builtins.xxx version or import all of them with with builtins;.

And there are more fun facts. builtins.builtins is the only builtin that has no __builtins toplevel counterpart. And builtins.builtins is builtins itself, tying the knot and making builtins.builtins.builtins.readFile a valid builtin. Nix has some quirky loopholes :slight_smile: .

3 Likes

It is also interesting that makes some builtins so special, that they are available at top-level without requiring __ or builtins. prefix
It is understandable for true, false, abort, derivation, placeholder and import (although not fully - being builtins and not keywords makes they overridable, one can write let true=false; in ... but no let 1=2; in), the same exception made for

  • isNull (but not for builtins.isInt)
  • baseNameOf
  • dirOf
  • removeAttrs (but not for builtins.hasAttr)
  • map (but not for builtins.filter)
  • toString
  • fetchTarball (but not for builtins.fetchurl)
    looks mystical.

UPD: it looks like the top-level list is different in HNix and C++ Nix: HNix extends it with mapAttrs and trace

1 Like