Both uses of the \let primitive often lead to the same result. Is this even true for every case? What was the thought process behind it. And if they are actually not equivalent, are there particular situations in which you can only use one or the other?



As your plenty and nice answers show, there are cases in which you clearly have to respect one of the variations (for instance regarding assignments to '=' and macros that check for spaces). These were totally understandable points and they explain a lot. Now, @Werner's answer confirmed one ingenuous thought of mine:

"The use of = is purely to emphasize the fact that an assignment is being made [...]"

Moreover Knuth himself seem to have intended the synopsis including the '=', though he also used the other way. Why did he include it then? It will probabilly have to do with the exact meaning of the TeX source snippet cited by @Werner. To be honest, I don't know WEB either, but if there is someone who does, can he explain it (in very easy terms) by adding an edit to his answer? New answers are clearly welcome too.

Side Note

As an element of a gray area I found an example where using the syntax including the equal sign causes "difficulties":

If you have control sequences which need the 'csname-expandafter' mechanism, and you want to stick to the minimal exploitation of \expandafter by saying

\expandafter\let\csname foo1\expandafter\endcsname\csname bar1\endcsname

you can't add an equal sign because it would distract TeX from reading the \expandafters correctly. To get the equal sign work you would need to use the slightly unhandier version

\expandafter\let\csname foo1\expandafter\endcsname\expandafter=\csname bar1\endcsname
  • 1
    I've added some discussion on WEB to my answer...
    – Werner
    Nov 14, 2013 at 23:33
  • @Werner, Good. So, line 6 is where the magic happens (to reduce your elaborated discussion :-) [The details help a lot, in fact]) What do you think about the last comment on my answer below?
    – Ruben
    Nov 14, 2013 at 23:57
  • @Werner Interesting that the comment on the code of the source leads to \let<csname><opt equals><opt space><token> (see also egreg) while the answers here show examples where it is not optional, hence not true in general.
    – Ruben
    Nov 15, 2013 at 0:01
  • Yes, well, that's an outlier, where you use the optional equals as the token. Of course, how is TeX to discern between which one is which when they're both the same... In a similar vein, if you want to pass [ as a mandatory argument of a macro that can take an optional argument (say, \newcommand{\macro}[2][x]{#1#2}), using \macro[ would cause problems. You need to use \macro{[}. However, \macro[][ is fine. If you want to use something that is optional in a mandatory fashion, you have to be careful.
    – Werner
    Nov 15, 2013 at 3:00
  • What's wrong with \expandafter\let\csname foo1\expandafter\endcsname\expandafter=\csname bar1\endcsname? Surely the = is superfluous, but it still works.
    – Werner
    Mar 26, 2014 at 15:58

5 Answers 5


There is a case where = is mandatory, besides the obvious one when you want to \let a token to be the same as =:


is the only correct form.

The syntax rules for \let are

\let<control sequence><equals><one optional space><token>

where only <equals> requires an explanation: it is

<optional spaces> | <optional spaces>=

where = must have category code 12 (and be explicit). Note that no expansion is attempted during a \let assignment, so the optional spaces must be explicit. The following example should not be taken seriously, but it works:

\expandafter\let\expandafter\cs\four= \relax

would make \cs equivalent to \relax. This is admittedly esoteric.

However there's a place where the = and the following optional space are mandatory rather than optional.

Suppose we want a macro that tests if the first token in its argument is a space and takes some action if it is. In the following example, the space will be removed.


\def\foo#1{\afterassignment\@checkspace\let\next= #1}
    \typeout{Space found and removed}%
    \typeout{Space not found}\next

|\foo{No space}|
|\foo{ A space}|
|\foo{~ isn't a space}|

enter image description here

The terminal output will be

Space not found
Space found and removed
Space not found

If only \let\next is used, a space would indeed be removed because of the syntax rules, but we'd not be able to distinguish if a space was there or not; worse, if the token list begins with = we'd be in big trouble. With \let\next= and no space, a leading = would be honored, but still an initial space would be removed with no notice.

There are other methods, for example with \futurelet, to solve the same problem. One might enjoy looking at how booktabs.sty defines \futurenonspacelet using a trick similar to this one.

Actually Knuth is quite coherent in his usage of the optional = in plain.tex. All top level \let instructions have it, because this can only impact by a few microseconds in the processing of the instruction, while inside definitions the = is rarely used. It appears in the definition of


but in all the other replacement texts it's omitted, because it impacts on memory usage: each token in a replacement text occupies space and, when TeX was developed, memory space was a concern.

Conversely, in manmac.tex all \let instructions have = (except, quite strangely, in the definition of \footnote). The difference is that, in case memory is scarce when typesetting the TeXbook, manmac.tex can be easily edited, while plain.tex is “for everybody” and so must be optimized.


There is one case where you must use =. You write a macro that ends with a \let command, and you want to be able to tell whether a space follows it. In the following we use a trick to make \spacetoken equal to a space (e.g., latex.ltx does this with \@sptoken).

\def:{\let\spacetoken= }\: %

The analysis is that \let\spacetoken= allows one optional space to follow the equal sign, and that is the one in the macro definition, thus \spacetoken equals a space after \: is executed. This can be used to test if a space follows a macro argument:

\def\X#1{\def\temp{#1}\afterassignment\Y\let\next= }
\def\Y{\ifx\next\spacetoken\message{Found a space after \temp!}\fi}
\X{abc} % space following the argument.

In this case, \next can become equal to a space and \Y can test that. The \message produced above is "Found a space after abc!".

  • Similar to mine; but I used \@sptoken without explaining how it's defined. ;-)
    – egreg
    Oct 28, 2013 at 21:43
  • I believe a backslash is missing after the \def in \def:{\let\spacetoken= }\:. At least, my latex.ltx has this: \def\:{\let\@sptoken= } \:
    – frougon
    Apr 28, 2018 at 9:32

No, this is not true in all cases. One particular case is the difference between


(very likely leading to the error: Undefined macro \myrelax) and


(doing what we want).

Otherwise, there should be no difference. I prefer the variant without =. Notice that this is not the only case where you can put = or not. It is similar for all TeX-core-style assignments to counters, dimensions and glues.

There's actually one case that wouldn't work with =, and that is when you want to \activeate = and \let it something:

% output: 123


% error: Undefined control sequence. =
% if you continue by `q`:
% output: 2 13

From The TeXbook (Chapter 24 Summary of Vertical Mode, p 275):

It's time now to return to our original goal, namely to study the commands that are obeyed by TeX's digestive organs. Many commands are carried out in the same way regardless of the current mode. The most important commands of this type are called assignments, since they assign new values to the meaning of control sequences or to TeX's internal quantities. For example, \def\a{a} and \parshape=1 5pt 100pt and \advance\count20 by-1 and \font\ff = cmff at 20pt are all assignments, and they all have the same effect in all modes. Assignment commands often include an = sign, but in all cases this sign is optional; you can leave it out if you don't mind the fact that the resulting TeX code might not look quite like an assignment.

The use of = is purely to emphasize the fact that an assignment is being made, but it is optional in all cases. This is verified when viewing the source of TeX (I don't know WEB, but a view of the code definitely reflects grabbing tokens, condition on whether = was found and then subsequently grabbing another token by skipping over a spaces) - line numbers added:

 1:  @ @<Assignments@>=
 2:  let:  begin n:=cur_chr;
 3:    get_r_token; p:=cur_cs;
 4:    if n=normal then
 5:      begin repeat get_token;
 6:      until cur_cmd<>spacer;
 7:      if cur_tok=other_token+"=" then
 8:        begin get_token;
 9:        if cur_cmd=spacer then get_token;
10:        end;
11:      end
12:    else  begin get_token; q:=cur_tok; get_token; back_input;
13:      cur_tok:=q; back_input; {look ahead, then back up}
14:      end; {note that |back_input| doesn't affect |cur_cmd|, |cur_chr|}
15:    if cur_cmd>=call then add_token_ref(cur_chr);
16:    define(p,cur_cmd,cur_chr);
17:    end;

According to the WEB User Manual*, the above code snippet for \let (which coincidentally also is used by \futurelet) can be interpreted in the following way (numbers below reflect the line numbers above. The discussion below assumes an input of the form \let<csname><opt equals><opt space><token> (WEB documentation is interspersed):

  1. A new module is started (as a result of @␣). The module name is called Assignments.

    @␣: This denotes the beginning of a new (unstarred) module. [...];
    @<: A module name begins with @< followed by TeX text followed by @>; An equal sign (=) must follow the @> at the end of this module name; you are saying, in effect, that the module name stands for the Pascal text that follows, so you say '<module name> = Pascal text';

  2. let is a label within TeX that can be jumped to using a goto command, if needed. A group is started with begin, and n is assigned the value of cur_chr. cur_chr contains the "code" associated with the current token already read in. At this point, TeX has read \let.

  3. The procedure get_r_token is called which makes sure you can actually make an assignment to a control sequence that can be assigned something. Whatever <csname> is read in (either correctly specified or inserted by get_r_token if it was incorrectly specified), it is assigned to p.
  4. Here the Pascal code checks whether n is equivalent to normal - the "code" associated with \let. Viewed differently, lines 5-11 now deal with how \let is handled, while lines 12-14 deals with \futurelet (n not equivalent to normal).
  5. The if-clause is started with begin and a loop is started with repeat. A following token is also read in by calling get_token.
  6. get_token is called until whatever is read in is not a space anymore (called spacer). At this point, TeX either reads in the optional equals <opt equals> or the <token> (skipping over spaces if there was no =).
  7. Here a check is made to see whether a = was found.

    The WEB language has another feature that is somewhat similar to a numeric macro. A preprocessed string is a string that is like a Pascal string but delimited by double-quote marks (") instead of single quotes. [...] If a preprocessed string is of length one (e.g., "A" [...]), it will be treated by TANGLE as equivalent to the corresponding ASCII-code integer (e.g., 65 [...]).

    So, in fact, other_token+"=" is a numeric value that represents an equal sign.

  8. The if clause is started with begin and the following token is retrieved via get_token.

  9. if this token is a space (spacer), then another token is grabbed via get_token.
  10. end inner if clause (line 9).
  11. end clause dealing with = (line 7).
  12. \futurelet
  13. \futurelet
  14. \futurelet
  15. ?
  16. Define <csname> as being the same <token.
  17. end (line 2)

Knuth himself always uses

Synopsis: \let<control sequence>=<token>

It is the same behaviour as for dimension/counter registers:

\parindent0pt versus \parindent=0pt

Internally it is tested if there is a =, so it makes no difference to have a valid \let operation between two macros with or without =.

  • 1
    Actually, if you scan The TeXbook, you'll see the Knuth mostly uses \let<cs>=<token>, but there are a few cases where he uses \let<cs><token> (very few though).
    – Werner
    Oct 28, 2013 at 21:00
  • 2
    i think jonathan fine did work on timings of \let\a\b vs. \let\a=\b back in the 90s. iirc he found a very small difference in favour of \let\a=\b (which i thought slightly paradoxical, at the time). he was using emtex on a machine that was (even for the time) pretty slow. (could be a false memory, though.) Oct 28, 2013 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.