Why not `{ let commonConfig = ...; in ...; }`?

NixOS 23.05 manual | Nix & NixOS says:

You can write a let wherever an expression is allowed. Thus, you
also could have written:

  services.httpd.virtualHosts =
    let commonConfig = ...; in
    { "blog.example.org" = (commonConfig // { ... })
      "wiki.example.org" = (commonConfig // { ... })

but not { let commonConfig = ...; in ...; } since attributes (as
opposed to attribute values) are not expressions.

Why not { let commonConfig = ...; in ...; }? What is the place where the let expression appears expected to be used for?

Are attributes the same as option names? Are attribute values the same as option values?

What that snippet is saying is that the let syntax creates an expression.

The { } attribute set syntax also creates an expression, and the rules for that syntax are that it must contain zero or more attributes, where an attribute is of the form name = value-expression;. The name of an attribute is what is on the left of the equals sign, and the value of the attribute is what is on the right.

But { } can only contain attributes. { 1 + 1 } is not valid syntax because 1 + 1 isn’t an attribute; it has no name and no equals sign. { foo = 1 + 1; } is valid syntax because foo = 1 + 1; is an attribute.

Similarly, { let commonConfig = ...; in ...; } is not valid syntax because let commonConfig = ...; in ...; isn’t an attribute. There is an equals sign in there but that’s part of the internal syntax of let. You have to think of everything ‘inside’ the let ... in ... construct as a single unit, and that unit is an expression. (The right side of this syntax appears open-ended, but it stops at the delimiter expected by whatever syntax the let appears within, like a semicolon or a closing parenthesis—or, in appropriate cases, the end of the file.)

All of the above has to do with the syntax of the Nix language. NixOS options are an abstraction that exists above the Nix language. An attribute can be used to express that an option has a value, but they aren’t ‘the same’, any more than a compiler flag is ‘the same’ as a command line argument, even though a command line argument might be used to provide a compiler flag.