How come I can't print out the hexadecimal value of a character?

fn main()
{
    let x = 'c';
    println!("{:x}", x); // Error over here
}

I undertand that in the compiler I have to implement the trait std::fmt::LowerHex but if characters are stored as binaries just like with intergers, how come I can't just output it as a hexadecimal?

println!("{:x}", x as i32);

I see, but like with C I know I can print it in hexidecimal from a character as everything is stored as binary

#include <stdio.h>

int main()
{
    char x = 'c';
    printf("%x", x);
}

How come the same logic doesn't apply to Rust, doesn't Rust also store everything (including characters) as binaries? I am just curious to know that is all?

Everything in memory stored as binaries in every language, since it's the only format our RAM can store. But it doesn't means languages should allow print everything in hex by default. In C you can print hex of its char type. In Rust you can't without conversion. Semantics varies over languages like java, python, JS, ruby etc

2 Likes

Is it cause the compiler itself has been programmed not to print stuff out as a different format, even if it makes logical sense unless if it is converted?

Char in C is casted implicitly to int on printf.

2 Likes

So both languages don't support printing hex of its char type but the C performs implicit type casting.

1 Like

I guess Rust doesn't do implicit type casting then? Is this due to safety reasons?

There are no implicit type casts because it makes your code easier to reason about.

3 Likes

The only semi-implicit conversion you'll ever see is when calling Into::into and the compiler knows which type it should convert into. You still have to explicitly implement the trait for every conversion and the method call is explicit, too, but once that's done, it's really easy to use and you don't need to specify what type to convert to. It doesn't work for cases, where multiple conversions might be allowed, though, e.g. when working with generic type parameters.

1 Like

Conceptually, Rust makes a distinction between characters and their encodings, and do not provide implicit conversion between them. Hexadecimal printing logically works on the encoding, not the character itself, so printing a character in hexadecimal requires an explicit conversion.

Rust does provide a limited set of implicit coercions. Moreover, the dot operator invokes auto-dereferencing. However, these implicit conversions are much more limited than what C provides, because of the potential confusion that they may cause.

1 Like

escape_unicode May also be worth mentioning here.

println!("{}", 'c'.escape_unicode());

prints \u{63}

fn main ()
{
    let x = b'c';
    println!("{:x}", x);
}

That is, C's "char" literal ('c') equivalent in Rust is b'c', since in both cases it will correspond to a byte-sized integer.

Rust's char is a higher-level construct: a Unicode scalar value, for which not 1 byte nor even 2 bytes can hold all the values, hence its using u32 as its backing integer.

Since a char and an integer convey different semantics, the sane approach is to require some explicit transformation when moving between each.

As a counter-example, I don't think C should brag about the ergonomics of implicit conversions, when the following code compiles fine with no warnings whatsoever, and yet exhibits Undefined behavior:

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char const * const argv[])
{
    puts("argc = " + argc);
    return EXIT_SUCCESS;
}
1 Like

C confuses bytes with characters. If you replicate that confusion in Rust by using b-prefixed character literal, it will work just like in C:

fn main() {
    let x = b'c';
    println!("{:x}", x); // Prints 63
}
1 Like

What does escape_unicode() exactly do?

It turns the character into a string of the form \u{NNNNNN}, where the number is the hexadecimal representation of that unicode codepoint.

2 Likes