println is a macro in Rust because 1) the language currently lacks support for variadic functions and 2) being a macro allows the format string to be parsed at compile time.
However, there is one more thing a function can't do: borrowing arguments. Formatting never moves arguments because the format_args! macro generates references to them, which it then creates std::fmt::Argument out of.