13 ms·
Format Strings in Rust 1.58
- nindalf 5y agoNice post. For some reason I can never remember the syntax for formatting and I have to look it up each time. I’ll bookmark your post for the next time I need this.
- nicoburns 5y agoIt might help you to remember that the syntax is `{identifier:flags}` where identifier being left blank signifies a positional argument to the formatting macro. Although if it's the specific flags you're having trouble remembering, I can't help you as I can't remember them either except `?` (debug print) and `#?` (pretty debug print).
- nindalf 5y agoThanks Nico!
- 1f60c 5y agoA little hack: if you know the C-style format string you want to use, use that. The build will fail, of course, but Rust will tell you what to use instead.
- estebank 5y agoEven more: those suggestions that you see when calling cargo/rustc? They are also applicable through a tooltip with rust-analyzer. rustc has json output where the substitutions are represented in a machine-friendly format. :)
- jackosdev 5y agoThanks really appreciate it, happy to be in someone's bookmarks
- bobbylarrybobby 5y agoThe full spec is here: https://doc.rust-lang.org/std/fmt/index.html https://doc.rust-lang.org/std/fmt/index.html
- japanuspus 5y agoI appreciate the effort that went into the blog post, but for this forum a link to the release notes [0] and/or the updated docs [1] would have been more relevant. [0]: https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html#captured-identifiers-in-format-strings https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html#captu... [1] https://doc.rust-lang.org/std/fmt/ https://doc.rust-lang.org/std/fmt/
- jackosdev 5y agoThanks for that, I put both those links up the top of the blog post
- deleted 5y ago[deleted]
- megumax 5y agoIt's a nice Quality of Life addition, that I would like to see with C++ as well. I wish they don't implement f-strings and s-strings, at least for now. Even if they are more ergonomic than the `format!` and `String::from`, they hide a memory allocation which is not really indicated in a language like Rust and would be really weird in a context without an allocator. The only solution to this would be `const` evaluation of this, but that would restrict their use to `const` environments, so mostly unusable.
- pie_flavor 5y agoI mean, that ship's basically sailed. People are too lazy even to take `std::string_view` instead of `std::string&`, trying to avoid pointless allocations and clones with any API but your own is almost a fool's errand at this point.
- johncolanduoni 5y agoI think the issue with string_view is less laziness and more recency: most libraries still need to support C++ versions prior to C++17. Rust had the advantage that slices were there from day one.
- pjmlp 5y agoFor example, Bloomberg is still on their transition to C++14, let alone C++17. "C++11/14 at Scale: What Have We Learned?" https://www.youtube.com/watch?v=E3JG2Ijjei4 https://www.youtube.com/watch?v=E3JG2Ijjei4
- ATsch 5y agoI don't think it's just recency either, it's just incredibly easy to shoot yourself into the foot with string_view when the language has no way of checking that the pointed to memory is actually valid. People moved to smart pointers for good reasons and string_view just undoes all of that.
- pjmlp 5y ago
- Seattle3503 5y agoBlog posts don't normally get "Show HN" titles.
- andyjohnson0 5y ago> Blog posts don't normally get "Show HN" titles. Apologies for going meta, but I agree. To the OP: The guidelines for Show HN [1] say: "Show HN is for something you've made that other people can play with. HN users can try it out, give you feedback, and ask questions in the thread." Blog posts are specifically off topic. You seem to have posted this link ten hours ago and then re-posted the same link as a Show HN two hours ago - perhaps to get around the duplicate link detection. Please don't do that. [1] https://news.ycombinator.com/showhn.html https://news.ycombinator.com/showhn.html
- spoiler 5y agoReally amazing summary! I wasn't aware of some of these specifiers. I was alway a pretty basic fmt user due to my ignorance. I should go revisit some manual formatting code. :)
- joss82 5y agoSo, does that mean every string's { need to be doubled to {{? Doesn't that open a big security hole where someone could inject a special string to probe for variables' content?
- jhugo 5y agoNo, this is functionality of format strings (interpreted by the formatting macros like println!, format! etc), not strings in general. {} was already special in format strings, there's no backward compatibility break. You can't really pass untrusted input as a format string because they have to be available at compile time.
- joss82 5y agoOK, thanks for explaining that point :). I was expecting this but couldn't find it in the doc nor the release notes.
- Someone 5y agoSo, that rules out using format strings read from configuration text files. Not a big loss, IMO (you can have a DE.rs file with format strings for German, an IT.rs one for Italian, etc. and compile those in the binary), but I think some organizations will find that inconvenient. Also, FTA: “Remember that Rust doesn't use any localization, so these outputs will always look the same.” So, what do rust programs do for localization, e.g. to print the thousands separators users expect? Is there a library that gives you similar string interpolation, taking locale into account? It’s a tough call between catering for computers by ignoring locale and for humans by applying it, but I think I would have chosen for this to be locale-sensitive, with support for forcing a standard locale.
- zaarn 5y agoThere is a few libraries that can do some work around localization and formatting, though IIRC most commonly if you need something translated, you just turn it into a format argument as well. So instead of say "hello {name}", you write "{greeting} {name}" (though this doesn't cover all languages anyway). edit: There is also the issue that on some platforms (Linux) locale has some soundness issues (not a fault of rust) and yet on others it can be hard to use locales properly (Windows can introduce some hard locale weirdness)
- pron 5y agoCode injection is currently the #1 language-related security vulnerability [1][2] in memory-safe languages, which is why languages should be very careful when adding string interpolation as it may well be their most security-sensitive feature: "Templated string injection attack prevention will be of primary concern. The result of template processing can to be used in sensitive applications, such as database queries. Validation of templates and expression values prior to use can prevent catastrophic outcomes." [3]. [1]: https://owasp.org/www-project-top-ten/ https://owasp.org/www-project-top-ten/ [2]: https://www.softwaretestinghelp.com/sans-top-20-security-vulnerabilities/ https://www.softwaretestinghelp.com/sans-top-20-security-vul... [3]: https://openjdk.java.net/jeps/8273943 https://openjdk.java.net/jeps/8273943
- pcwalton 5y agoIn reality, sqlx [1], probably the most popular SQL library for Rust, has a query! format string that ensures that all parameters are properly escaped. As far as I can tell, you can't use the new format string support to create SQL queries with that macro yet, so there is no security problem. When that's fixed and query! is updated for the new format string support, I'm certain that they will escape their parameters, so there will be no security problem then either. Because all format strings are in macro context, where the macro has full control over what to do with all substituted parameters, Rust already has sanitized string interpolation. In terms of that JEP, the macro invocation is the policy object. [1]: https://github.com/launchbadge/sqlx https://github.com/launchbadge/sqlx
- samwillis 5y agoWhat I particularly like about Pythons new(ish) “f strings” is that, firstly it’s opt in, you have to mark your strings with the “f” prefix. Secondly it is converted to opcodes at compile time, so f strings can’t be modified at runtime unlike standard “old fashioned” Python string interpolation. As someone noted in another comment, this with Rust is effectively opt in with the println! and format! macros. Does it converted to machine code at compile time?
- kimundi 5y ago
- pabs3 5y agoHow would one print a literal "Hello {x}!" without the formatting?
- andrewaylett 5y ago`{}` has always been special in format strings, so no real change there. If you've got a string that happens to contain "Hello {x}!" and you want to print it, you must template it in, as the format string needs to be a literal: print!("{}", hello_string) Or as others have pointed out, you can double-up the braces: print!("Hello {{x}}!") Then if you want to be cute, you could do something like this: print!("Hello {x}!", x="{x}") Note that the named parameter syntax isn't new -- what's new is capturing named parameters from a scope that's larger than the format macro: let x = "{x}" print!("Hello {x}!") And also note that it's a compile-time error to have an unused format parameter in a string, so that last code example wouldn't have compiled at all in older versions of rust.
- SkeuomorphicBee 5y ago> Also to escape these curly braces, just put two of them in front of eachother So that would be "Hello {{x}}!".
- georgyo 5y agoIs this feature was introduced in 1.58, does that mean strings in 1.57 didn't need the extra curly braces? I imagine this is going to break a lot of existing code. There doesn't even seem to be a way to have {x} mean the same thing in 1.57 and 1.58
- deleted 5y ago[deleted]
- lights0123 5y agoNo. That was invalid syntax previously.
- 5y ago
- stephc_int13 5y agoThis "modern" style of string formatting might seem pretty convenient and concise, but in my opinion, it has quite a few drawbacks. I'd prefer something that would maybe be less concise but easier to read and maintain, using the host language instead of a mini script using in-band signaling and its weird syntax.
- thomasahle 5y agoIMO having a mini language for strong formating makes much more sense than trying to force those capabilities into the host language.
- ChrisSD 5y ago`printf` isn't exactly modern!
- VWWHFSfQ 5y agomaybe they just don't like string formatting at all? I'm not sure what they mean.
- stillicidious 5y agoDefinitely concur. In a well designed language, I'd expect it to be possible to express something a bit closer to C++'s ios but without the crazy verbosity, and without switching mode. It's not that general language features like this aren't possible, it's often that they simply haven't been discovered yet. User-defined literals are a recent concept that helps eliminate some crap in a related area. Meanwhile, can't complain all that much if zero-cost features can be added to Rust to make it easier to market it to scripting folk. I think that can only be a good thing, even if the feature design is far from ideal.
- wongarsu 5y agoI'm not sure what kind of alternative you are imagining. The style's I'm aware of are "Hello {username}!" "Hello $username!" "Hello " . username . "!" "Hello " + username + "!" "Hello " << username << "!" Of those, I find the first and second one by far the easiest to read, and the first one is easier to extend (as rust has done, e.g. "USD{total:>6}" for a left-padded number).
- renox 5y agoNice, I hope it will be improved to print the variable names as in Python f-string, something like "{x=}" generating "x=<value of x>", much better than having to write "x={x}" everywhere..
- Svetlitski 5y agoThis is what the dbg! macro is for: https://doc.rust-lang.org/std/macro.dbg.html https://doc.rust-lang.org/std/macro.dbg.html
- bobbylarrybobby 5y agoUnfortunately the `dbg!` macro doesn't play nice with format strings. There is no Rust equivalent of Python's (say) `print(f"Coordinates: {x=}, {y=}, {z=}")`. In other words `dbg!` can only print `{x=}`; it can't intersperse that with other text the way `format!` can.
- tialaramex 5y agoThis should not be difficult to do yourself. That is, you should be able to provide an improved format! with this feature as say renox::format! and provide popular format-using macros like renox::println! and renox::print! too based on the existing code, simply using the same license as Rust's standard library. Only the macro knows the name of the variable, so you can't do this inside the formatter itself.
- bluejekyll 5y agoGenerally if you want that, the derived Debug impls are good enough, and then you can print the debug form with: "{x:?}" and that can be made to pretty print (newlines, etc) with "{x:#?}". That will print the entire structure of a type and its associated values (can be quite verbose, though).
- thurn 5y agoHow did they stabilize this so fast? I feel like we first heard about this feature only a few months ago. Meanwhile I've been waiting for `let_chains` since what feels like the mid 90s...
- talideon 5y agoLots of useful prior art to work off of from Python and C#, I'd say.
- lmkg 5y agoThe link below is the pull request for the feature. The RFC dates to 2019, and the tracking issue is slightly more than two years old. I'm guessing we only started hearing about it when it became close to stabilization (around November last year?). https://github.com/rust-lang/rust/pull/90473/ https://github.com/rust-lang/rust/pull/90473/
- kibwen 5y agoThis feature has been in the works for quite a while. I suspect you're thinking of a different but possibly-related change, where the 2021 edition has reserved the syntax for new string literals forms (beyond the existing b and r forms). In theory those new forms could be used to permit f-strings. But the new capability mentioned in the OP is the ability to implicitly capture names in format strings, which works with the existing formatting macros (such as println). That said, Rust isn't a company, it's a volunteer organization, and features advance at the speed of enthusiasm. If there's a feature that someone wants to see, there's no use waiting around for it, someone's got to be the one to push it forward. :)
- didip 5y agoWill there be a support for `{jndi:ldap://xyz}`? I jest :)
- nine_k 5y agoIt's a fair question. The Rust's implementation apparently only looks up variables from the enclosing scope. By contrast, Python's f-strings allow arbitrary expressions, and could potentially fall for the LDAP trick, or something similar. Same with ES6's backtick-strings. I hope Rust will keep it simple and reliable, and won't allow calling functions in format strings.
- AlphaSite 5y agoPythons f strings are a “compile time” thing, I don’t think you can get that effect unless you eval the string.
- kibwen 5y agoFurthermore, format strings in Rust can't just be any `String` or `&str`, they must specifically be string literals, which means they must necessarily be fully determined at compile-time and there's no chance that user input can influence the format string. > I hope Rust will keep it simple and reliable, and won't allow calling functions in format strings. Yes, I think people are wary of allowing full expressions in format strings as in Python. That said, I might like to see a small extension to the current rules, so that in addition to identifiers you could also access struct members. I agree that format string captures shouldn't present the chance to run arbitrary code, so I wouldn't even extend this to array indexing (which is overloadable via the Index trait).
- dathinab 5y agoPythons f-strings are still static formation, you just can move party of the code into the fstring. (Ignoring eval and similar.) The scary part about the vulnerability was that string >inputs< could dynamically delegate it's content to some magic information fetching system which by default allows accessing remote content in a way which by design can lead to remote code execution. I have no idea who though non static format strings are a good idea. Or an information fetching system which can trigger remote code execution. Or that this system doesn't require stric whitelisting.
- optician_owl 5y agoDoes not allow to use a struct field. That's strange.
- kibwen 5y agoLike many Rust features, things that were not necessary for an initial release were left to future releases. I agree that struct fields would be a natural extension, although I would want it to end strictly there.