Why can I not use \string to prevent the expansion of \csname?

\immediate\write\@mainaux{\gdef \string\saveForLater{123}}

This should work since it saves \gdef\saveForLater{123} in the .aux.

\immediate\write\@mainaux{\string\expandafter\gdef\string\csname saveForLater\string\endcsname{456}}

The problem there is that you are saving \expandafter\gdef \csnamesaveForLater\endcsname{456} in the .aux, because \string doesn't add a space after outputting the control sequence name. So when you read again and see \csnamesaveForLater TeX doesn't know what that is. You would need \string\csname\space. But I don't know what you intend, simpler seems

\immediate\write\@mainaux{\gdef\expandafter\string\csname saveForLater\endcsname{456}}

which saves \gdef\saveForLater{456}. If that's not what you intend, you need that \space there.

Or, as you've already seen, use \noexpand.


Recall that \write does complete expansion until unexpandable tokens remain (when the write operation actually takes place). Since \gdef is unexpandable, it needs not be preceded by \string, unless you want to avoid a space following it.

There is a timing problem with your code (let me omit \immediate that's irrelevant here):

\write\@mainaux{\string\expandafter\gdef\noexpand\csname saveForLater@\somedef\string\endcsname{456}}
  1. the token \string is expandable, so it acts on the following token (\expandafter)
  2. \gdef is not expandable
  3. \noexpand is expandable, so it acts on the next token (\csname)
  4. the characters in saveForLater@ are unexpandable
  5. \somedef should expand to character tokens, let's say it expands to foo
  6. \string is expandable, so it acts on the next token (\endcsname)
  7. the characters in {456} are unexpandable.

Thus what you get written is

\expandafter\gdef \csname saveForLater@foo\endcsname{456}

With \string\csname you'd get

\expandafter\gdef \csnamesaveForLater@foo\endcsname{456}

because there is no space following \csname in the token list passed to \write.

Why is there one with \noexpand? For the same reason there is one after \gdef: TeX adds a space following an unexpandable control word when it performs a write operation. With \noexpand\csname the normally expandable \csname is made temporarily equivalent to \relax, so it gets written with a space following it, just like \relax would (or \gdef).

\immediate\write\@auxout{%
  \gdef\expandafter\noexpand\csname saveForLater@\somedef\endcsname{456}%
}

will write

\gdef \saveForLater@foo {456}

because \expandafter causes the control sequence being formed by \csname before \noexpand can act on it.

If you want to preserve the \csname in the .aux file:

\immediate\write\@auxout{%
  \noexpand\expandafter
  \gdef\noexpand\csname saveForLater@\somedef\endcsname{456}%
}

that will write

\expandafter \gdef \csname saveForLater@foo\endcsname {456}