The variable number of #s is in case the string contains some number of #s.
print is a macro so it can be strongly typed and checked by the compiler. As far as I know, "!" is just a convention to make macros obvious -- it seems unnecessary to me, too.
Putting the type of a variable at the end is common in new C-likes. Go does it too. In the case of Rust, I'm pretty sure it's inherited from ML. It avoids a lot of the problems and complexities caused by C's type keywords (see: typedefs of pointers to functions; const pointers to values and pointers to const values; etc.).
> As far as I know, "!" is just a convention to make macros obvious -- it seems unnecessary to me, too.
No, it's very necessary, as macros actually take arbitrary token sequences as an 'argument', so there's no guarantee that the contents will parse as Rust code (e.g. https://github.com/huonw/brainfuck_macros), furthermore, macros can transform this argument into arbitrary code.
Hence it's nice to have an in-source marker to avoid humans and compilers having to work out if a certain name has strange semantics (particularly for compilers, to avoid having to intertwine parsing and name resolution: makes this simpler).
While this isn't exactly the same, the convention of appending "!" in scheme/racket to denote a function that mutates a value is helpful in my opinion. You don't absolutely need it, but reading through your code and easily identifying where your values can change is nice.
print is a macro so it can be strongly typed and checked by the compiler. As far as I know, "!" is just a convention to make macros obvious -- it seems unnecessary to me, too.
Putting the type of a variable at the end is common in new C-likes. Go does it too. In the case of Rust, I'm pretty sure it's inherited from ML. It avoids a lot of the problems and complexities caused by C's type keywords (see: typedefs of pointers to functions; const pointers to values and pointers to const values; etc.).