\mathchardef for math characters behave in a similar way.
\tracingall
\chardef\foo=123
\mathchardef\baz=123
\csname @@end\endcsname\end
The .log contains:
{changing \foo=undefined}
{into \foo=\relax}
and
{changing \baz=undefined}
{into \baz=\relax}
Other commands:
\countdef
\dimendef
\skipdef
\muskipdef
\toksdef
The list comes from the source code tex.web (keyword shorthand_def):
@ A \.{\\chardef} creates a control sequence whose |cmd| is |char_given|;
a \.{\\mathchardef} creates a control sequence whose |cmd| is |math_given|;
and the corresponding |chr| is the character code or math code. A \.{\\countdef}
or \.{\\dimendef} or \.{\\skipdef} or \.{\\muskipdef} creates a control
sequence whose |cmd| is |assign_int| or \dots\ or |assign_mu_glue|, and the
corresponding |chr| is the |eqtb| location of the internal register in question.
@d char_def_code=0 {|shorthand_def| for .{\chardef}}
@d math_char_def_code=1 {|shorthand_def| for .{\mathchardef}}
@d count_def_code=2 {|shorthand_def| for .{\countdef}}
@d dimen_def_code=3 {|shorthand_def| for .{\dimendef}}
@d skip_def_code=4 {|shorthand_def| for .{\skipdef}}
@d mu_skip_def_code=5 {|shorthand_def| for .{\muskipdef}}
@d toks_def_code=6 {|shorthand_def| for .{\toksdef}}
@<Put each...@>=
primitive("chardef",shorthand_def,char_def_code);@/
@!@:char_def_}{.{\chardef} primitive@>
primitive("mathchardef",shorthand_def,math_char_def_code);@/
@!@:math_char_def_}{.{\mathchardef} primitive@>
primitive("countdef",shorthand_def,count_def_code);@/
@!@:count_def_}{.{\countdef} primitive@>
primitive("dimendef",shorthand_def,dimen_def_code);@/
@!@:dimen_def_}{.{\dimendef} primitive@>
primitive("skipdef",shorthand_def,skip_def_code);@/
@!@:skip_def_}{.{\skipdef} primitive@>
primitive("muskipdef",shorthand_def,mu_skip_def_code);@/
@!@:mu_skip_def_}{.{\muskipdef} primitive@>
primitive("toksdef",shorthand_def,toks_def_code);@/
@!@:toks_def_}{.{\toksdef} primitive@>
@ @<Cases of |print_cmd_chr|...@>=
shorthand_def: case chr_code of
char_def_code: print_esc("chardef");
math_char_def_code: print_esc("mathchardef");
count_def_code: print_esc("countdef");
dimen_def_code: print_esc("dimendef");
skip_def_code: print_esc("skipdef");
mu_skip_def_code: print_esc("muskipdef");
othercases print_esc("toksdef")
endcases;
char_given: begin print_esc("char"); print_hex(chr_code);
end;
math_given: begin print_esc("mathchar"); print_hex(chr_code);
end;
@ We temporarily define |p| to be |relax|, so that an occurrence of |p|
while scanning the definition will simply stop the scanning instead of
producing an ``undefined control sequence'' error or expanding the
previous meaning. This allows, for instance, `.{\chardef\foo=123\foo}'.
@<Assignments@>=
shorthand_def: begin n:=cur_chr; get_r_token; p:=cur_cs; define(p,relax,256);
scan_optional_equals;
case n of
char_def_code: begin scan_char_num; define(p,char_given,cur_val);
end;
math_char_def_code: begin scan_fifteen_bit_int; define(p,math_given,cur_val);
end;
othercases begin scan_eight_bit_int;
case n of
count_def_code: define(p,assign_int,count_base+cur_val);
dimen_def_code: define(p,assign_dimen,scaled_base+cur_val);
skip_def_code: define(p,assign_glue,skip_base+cur_val);
mu_skip_def_code: define(p,assign_mu_glue,mu_skip_base+cur_val);
toks_def_code: define(p,assign_toks,toks_base+cur_val);
end; {there are no other cases}
end
endcases;
end;
There is another, but different case, where a control sequence gets assigned to \relax: If the command sequence constructed by \csname ...\endcsname is undefined, then it is assigned to \relax.
The case \font, see Joseph Wright's comment:
\tracingall
\font\foo\foo
\csname @@end\endcsname\end
From the .log, \foo is temporarily defined to an non-expandable command:
{changing \foo=undefined}
{into \foo=select font nullfont}
tex.webat https://tex.stackexchange.com/a/376435/4427 – egreg Jun 24 '17 at 19:23