News for hackers. Stuff that matters.
Re:Rust Language Cheat Sheet by oblio on Sunday April 25, 2021 @08:31AM (#26932232)
I'm not sure I understand why format is a macro and print and equivalent in most other programming languages is a regular function. There's some complexity there that I guess I just don't know.

For the second part, Rust should probably add some sugar à la Python f-strings (or just plain old string interpolation dating back to the Bourne shell):

format!("{x}{y}")

Re:Rust Language Cheat Sheet by burntsushi on Sunday April 25, 2021 @08:40AM (#26932271)
> There's some complexity there that I guess I just don't know.

It's because the format string itself is checked to be consistent with the types of arguments given. If there's an inconsistency, you get a compile time error. Most printf-style functions in other programming languages don't do this.

Re:Rust Language Cheat Sheet by oblio on Sunday April 25, 2021 @08:42AM (#26932278)
Ah, that's awesome and it makes a lot of sense, plus it's super helpful.
Re:Rust Language Cheat Sheet by masklinn on Sunday April 25, 2021 @08:55AM (#26932332)
There are other more "negative" reasons too:

The language doesn't have varargs (to say nothing of type-safe generic variable-type args), so you'd have to pass in a slice of references e.g.

    print!(..., a, b, c)
would become

    print(..., &[&a, &b, &c])
Which is not exactly sexy, plus all these references would have to implement a matching trait, whereas currently formatting different values can involve different traits depending on the format specifier e.g. a plain `{}` is Display but `{:?}` is Debug and `{:x}` is LowerHex. You can have a type which implements LowerHex but neither Debug nor Display if that makes sense.
Re:Rust Language Cheat Sheet by CryZe on Sunday April 25, 2021 @08:36AM (#26932255)
This is actually planned already. Not sure how far along the implementation and progress to stabilization is though.
Re:Rust Language Cheat Sheet by oblio on Sunday April 25, 2021 @08:44AM (#26932287)
When you say planned, do you mean it's in some roadmap? I couldn't find anything after a quick googling session.

Edit: Found the RFC: https://rust-lang.github.io/rfcs/2795-format-args-implicit-i... - but I don't see any sign that it has been accepted :-?

Edit II: I had missed the PR and issue at the top :-)

Re:Rust Language Cheat Sheet by gpm on Sunday April 25, 2021 @09:18AM (#26932468)
Re:Rust Language Cheat Sheet by oblio on Sunday April 25, 2021 @11:33AM (#26933508)
Ah, nice! Anything that makes Rust more approachable will help.
Re:Rust Language Cheat Sheet by unrealhoang on Sunday April 25, 2021 @08:49AM (#26932305)
format and co (write, print, ...) are macros because Rust doesn't have variadic functions. Also, the format string and its parameters are checked at compile time (number of params match number of field, and type), other language I know don't have this feature.
Re:Rust Language Cheat Sheet by oblio on Sunday April 25, 2021 @08:59AM (#26932348)
> Rust doesn't have variadic functions

By design or because nobody has implemented them, yet?

Re:Rust Language Cheat Sheet by gpm on Sunday April 25, 2021 @09:12AM (#26932426)
Nobody has implemented them or designed them yet. No obvious reason you couldn't come up with a satisfying design, but there's consensus building work there around "this is a good feature and the right design" as well as implementation work.
Re:Rust Language Cheat Sheet by unrealhoang on Sunday April 25, 2021 @09:07AM (#26932398)
it's a hard design problem, actually. But I think with const generic, we are getting very close, variadic could be:

    fn foo<const N: usize, T>(varied: [&T; N]) { ... }
    // usage
    foo([&a, &b, &c])
    foo([&a, &b, &c, &d])
Re:Rust Language Cheat Sheet by iudqnolq on Sunday April 25, 2021 @01:39PM (#26934649)
If I'm reading your intent right that would monomorphize to a slice of a specific N, meaning you couldn't express format!("{}{}", 1, 1.0)
Re:Rust Language Cheat Sheet by gpm on Sunday April 25, 2021 @02:04PM (#26934949)
I think you're reading the comment you replied to right, but I don't (completely) agree with your conclusion.

There are two magic things the format macro does apart from varargs, it checks the format string against the length and format types of the argument at compile time, and it automatically borrows the arguments. Implementing format with varargs means you need to lose those.

Once you lose those calling it looks like

    format("{}{}", &1, &1.0)
under the above proposal the type signature of format would look like

    fn format<const N: usize>(fmt_str: &str, args: [&dyn Format; N]) -> Result<String, Error>;
Where `Format` replaces the various `Display`, `Debug`, etc traits and looks something like this

    trait Format {
        fn display(&self, fmt: &mut Formatter) -> Result<(), Error> {
             // Default implementation of this method
             return Err(Error::NotImplemented)
        }
    
        fn debug(&self, fmt: &mut Formatter) -> Result<(), Error> {
             // Default implementation of this method
             return Err(Error::NotImplemented)
        }
    
        ...
    }
I.e. the trick is that the conversion to a trait object happens (implicitly) on the calling side when you take a reference, so all the arguments are all of the same type.
Re:Rust Language Cheat Sheet by iudqnolq on Sunday April 25, 2021 @02:11PM (#26935023)
That's a much better way of putting it. I was mostly trying to point out the missing dyn, but I never explicitly said that
Re:Rust Language Cheat Sheet by geofft on Sunday April 25, 2021 @09:00AM (#26932355)
> I'm not sure I understand why format is a macro and print and equivalent in most other programming languages is a regular function. There's some complexity there that I guess I just don't know.

It's a really good question! It's a little bit complex but hopefully not too much, and it shines a light on some interesting differences between languages.

The answer is roughly that Rust is strongly typed, and println!, format!, etc. all accept arbitrary types. They all also accept a varying number of arguments, and Rust only allows you to define functions with a fixed number of arguments. Python, sh, etc. have neither of these restrictions.

What would the type (function signature) of println/format/etc. be if they were regular functions?

    fn println(format: &str, arg1: SomeType?, arg2??);
You could solve the variable-number-of-arguments problem in Rust itself somehow, maybe

    fn println(format: &str, args...: SomeType);
but you'd have a few problems remaining.

First, you'd need to figure out what that type is. The formatting macros don't just accept things that implement Display - they also accept things that implement Debug if you use {:?}, things that implement LowerHex if you use {:x}, etc. And the macro wants to return a compile-time error if you mismatch types and the formatting string, like

    println!("Hex {:x}, debug {:?}", [1, 2, 3], 4);
And second, and a little more simply, the macro wants to return a compile-time error if you just have the wrong number of things being formatted:

    println!("Three things: {} {} {}", 1, 2);
In order to do that level of checking based on the value of the string, it has to be some sort of compile-time extension. In Python, you'd just get a runtime error and in bash, you'd just get empty strings.

    >>> "{:x}".format("hi")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: Unknown format code 'x' for object of type 'str'
    >>> "{} {} {}".format(1, 2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    IndexError: tuple index out of range
Regular functions in Rust (and any other language) don't get to see their arguments at compile time; they only see their arguments when they're called.

C, of course, just ignores this problem entirely. You don't get an error if you mess up the format string; you just get invalid reads to memory.

    printf("Is this a pointer? %s", 1234);
    printf("What's the third thing? %d %d %d", 1, 2);
Also (especially) in C, it's very easy to accidentally let the format string be input external to your program:

    printf(username);
which means that an attacker can specify the format string and cause reads to memory you weren't expecting and cause data to be leaked (or worse, an attacker can cause writes: C's %m token stores the number of bytes written to the next argument to printf, interpreted as a pointer - or the next thing that happens to be memory, if you didn't pass an argument for it). Rust protects against this by making you use a string literal as the first argument.

Python also protects against this by having f-strings be a special part of the language syntax. You can't have an f-string that's a variable, so an attacker can't set their username to f"{DATABASE_PASSWORD}". So, in that sense, the f sigil in Python is also a macro, like in Rust - it's just shorter syntax.

Re:Rust Language Cheat Sheet by ryl00 on Sunday April 25, 2021 @09:34AM (#26932580)
> C, of course, just ignores this problem entirely.

Many C/C++ compilers special-case printf, and can actually catch some printf format string problems at compile time. For example, gcc 5.4.0 and msvc 19 warned about both of the dodgy printf usages in your example (the type mismatch and the wrong number of arguments).