You may get them out of the call,
fit <- lm(mpg ~ hp, mtcars)
head(all.vars(fit$call), -1)
# [1] "mpg" "hp"
or the names of the model.frame which is probably better.
names(model.frame(fit))
# [1] "mpg" "hp"
"language" is the (storage) mode or typeof of the object just as "double", "integer" or "list" are. See ?mode, for more explanation and nice examples. In the R language definition you find a detailed explanation—anyway a nice reading.
Update
The 'variables' attribute of the terms is such a 'language' object.
(vars <- attr(fit$terms, 'variables'))
# list(mpg, hp, am)
typeof(vars)
# [1] "language"
To make use of it, we may coerce it as.character and remove the first element which is the name of the call, i.e. 'list',
as.character(vars)[-1]
# [1] "mpg" "hp" "am"
or, it can be evaluated, what it might actually intended for. Obviously it only will work, if we state with which data object the information is available.
with(fit$model, eval(vars))
# [[1]]
# [1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 17.8 16.4 17.3 15.2 10.4
# [16] 10.4 14.7 32.4 30.4 33.9 21.5 15.5 15.2 13.3 19.2 27.3 26.0 30.4 15.8 19.7
# [31] 15.0 21.4
#
# [[2]]
# [1] 110 110 93 110 175 105 245 62 95 123 123 180 180 180 205 215 230 66
# [19] 52 65 97 150 150 245 175 66 91 113 264 175 335 109
#
# [[3]]
# [1] 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1
If we ask why it isn't stored as a regular list, I would answer, because the information is already stored in the fit$model. It would be duplicated in a sense, and the size of the object would grow. The data is probably somewhere needed as list, the information itself actually is easier available using fit$model.