12 ms·
Rust vs. C++ Formatting
- mr_00ff00 4y agoFinally, after 40 years technology has advanced far enough for C++ to have a print function instead of cout
- asguy 4y agoIt always had printf: https://cplusplus.com/reference/cstdio/printf/ https://cplusplus.com/reference/cstdio/printf/ Edit: It's almost like the whole world got a lot of work done with the tools they already had.
- Jensson 4y agoWhich is a C function and not safe at all. It is easy to make your own print function in C++ that is safe and easy to use, but there is no such function in std.
- adamdusty 4y agoWhy are you worried about how safe printing to terminal is? Genuinely curious, I don't work in software dev.
- jasode 4y ago>Why are you worried about how safe printing to terminal is? The "type-safe" means "type-checked" by the compiler for correctness to help prevent bugs. It doesn't mean "safety-as-in-not-dangerous".
- yakubin 4y agoIn C you can use “%g” printf format string (which indicates a value of type double), and then not pass a double to it, but e.g. an int. Easy mistake to make, when changing pre-existing code. On x86 what will happen is the code will compile, but the function is going to read its argument from a floating point register instead of an integer register as it should. This: 1. Is a bug, since a completely unrelated garbage value is going to be printed. 2. Leaks the value of a register, which may be a security issue. There are still other common issues which can easily turn into vulnerabilities, leaking private process memory, when people pass untrusted strings as format strings with the intention of printing them raw. So you want a safe print to prevent trivial bugs in general, and security vulnerabilities in particular.
- __david__ 4y agoYou’re not wrong, but on the other hand, every C compiler I’ve used for the past 25 years has at least a warning you can enable for that. And you can easily add some attribute to custom functions that make use of *printf() functions under the hood to get those also type checked by the compiler. In practice that’s been good enough for me (and catches exactly the type of error you describe).
- charcircuit 4y agoUsually the warning is enabled by default
- maccard 4y agoYou're not always printing to a terminal, char buf[10]; const char* foo = "wrong?"; int res = snprintf(buf, 20, "What could possibly go %d", foo); Will compile and do... something...
- jholine 4y agoSolved problem since ages ago. error: format '%d' expects argument of type 'int', but argument 4 has type 'const char*' [-Werror=format=] You only get into trouble when you use runtime format strings (like passing a user string as first argument to printf)
- maccard 4y agoThat doesn't work on MSVC, and ignores multiple other issues with the 3 lines of code I shared (and as you correctly identified doesn't work with runtime format strings. It also doesn't work if your code isn't a textbook example of being wrong. [0] is _slightly_ more contrived but still suffers all of the exact same problems, despite all of the information being available at compile time. [0] https://gcc.godbolt.org/z/9vK3W4bYh https://gcc.godbolt.org/z/9vK3W4bYh
- gary_0 4y agoprintf() was often used for logging in eg. web servers. If there's no way of strictly checking the size/type of what's being printed (HTTP headers, say) then there are lots of tricky ways you can use it to write arbitrary memory and pwn the server. Type-unsafeness in general also just allows for hard-to-find bugs, since only certain data at runtime will introduce undefined behavior.
- stevenhuang 4y agoThere is now in C++20 https://en.cppreference.com/w/cpp/utility/format/format https://en.cppreference.com/w/cpp/utility/format/format Which is just the great https://fmt.dev/latest/index.html https://fmt.dev/latest/index.html that even c++11 projects can use.
- tialaramex 4y agoIt had C's printf, which means it isn't type safe, which is consequently a terrible primitive for this work. Like, it makes sense in C, which thinks boolean is a fancy new concept and thinks 0-terminated strings are a good idea, but it's not actually good. std::println is more or less what you would obviously build for a modern language and it's notable because C++ could have provided something pretty similar even in C++ 98, and something eerily similar in C++ 11 but it chose not to.
- hn_go_brrrrr 4y agoI think you'll find implementing a type-safe print function in C++11 very challenging. What you could do in constexpr was very limited, type deduction was less powerful, and it didn't have fold expressions. I'd say this really only became feasible since C++17.
- saghm 4y agoMaybe the top comment in this thread wasn't so facetious after all then; it did take almost 40 years for C++ to advance enough to have a decent print function
- Kranar 4y agoBoost had a type-safe printf function dating back to October 10th, 2002: https://www.boost.org/doc/libs/1_31_0/libs/format/ https://www.boost.org/doc/libs/1_31_0/libs/format/
- 1ris 4y agoAnd outstreams where not that bad, aswell. Sure, the operator overloading looks a bit rough. But that's IMHO a pragmatic choice if you want to offer customisation points and didn't have variadic functions yet. They where introduced only in c++11.
- tialaramex 4y agoC++ did have variadic functions because it inherited them from C. What it didn't inherit from C was a way to write variadic functions with variadic types, so that had to be home grown.
- 1ris 4y agoprintf is a bad joke of a formatting function. When i want to print a string i don't want to worry about the security implications of that. With printf i have to. [0] And i certainly don't want a turing complete contraption. [1] Also looking at log4j. And even if everything is correct, it's has to parse a string at runtime. I consider that alone unaesthetic. >Edit: It's almost like the whole world got a lot of work done with the tools they already had. The best metaphor i know for this attitude is "stacking chairs to reach to moon". If you don't care about the limits of the tech you will be stuck within it. I'm time and time again amused how anti intellectual and outright hostile to technological progress the programming profession is. programmers, out of all of them. [0] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=printf https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=printf [1] https://news.ycombinator.com/item?id=25691598 https://news.ycombinator.com/item?id=25691598
- asguy 4y ago> If you don't care about the limits of the tech you won't be able exceed what you think is possible. Did you propose/implement/release something better than printf? > I'm time and time again amused how anti intellectual and outright hostile to technological progress the programming profession is. programmers, out of all of them. Perfect is the enemy of good. Some people talk about getting work done, some people get the actual work done and move on.
- spoiler 4y ago> Perfect is the enemy of good. Some people talk about getting work done, some people get the actual work done and move on. In my experience, people with this motto generally produce code which frustrates the whole team. Being a perfectionist is toxic in its own way, though. There needs to be a balance. I think that balance is to think and plan a few steps ahead (not too much, as it's counter productive) before hitting the keyboard. I know this sounds a bit like a "d'oh, of course" but it really—and unfortunately—isn't something that people practice; they just think they do.
- 1ris 4y ago>Did you propose/implement/release something better than printf? This is what the article is about? Things much better that printf are a dime a dozed and available since 20 years. >Some people talk about getting work done, Like this article does? While you busy arguing that you could do the same thing, but much worse?
- arcticbull 4y ago> Edit: It's almost like the whole world got a lot of work done with the tools they already had. This feels a little defensive, but also pretty out of line with the philosophy of the C++ standards committee. The committee has been aggressively stapling every new leg they could find to that dog for decades. They just chose not to staple this particular leg on until now.
- simplotek 4y ago> This feels a little defensive, but also pretty out of line with the philosophy of the C++ standards committee. The committee has been aggressively stapling every new leg they could find to that dog for decades. They just chose not to staple this particular leg on until now. Your comment doesn't bear any resemblance with reality. C++ started with a spartan standard library and only recently did it standardized it's file system API. Compare that with what, say, POCO already offers. Or Boost. Or java/C#/Python/etc. What exactly led you to believe that absurdity?
- arcticbull 4y ago> What exactly led you to believe that absurdity? The fact that the standards committee simply chose to just add every feature every other language has.
- simplotek 4y ago> The fact that the standards committee simply chose to just add every feature every other language has. Again, this take is outright wrong and totally clueless. I mean, the summary of each change introduced by any of the C++ standards is freely available. C++20's most compelling features beyond concepts and modules were small improvements over existing features like lambda captures and template resolutions, or new atributes. What compells you to make such nonsensical claims?
- arcticbull 4y ago> What compells you to make such nonsensical claims? Literally all the additions between C++03 and C++20. Here's a small list since you'd rather attack me than review the changelogs, it seems. - range-based for loops. - enum class. - digit separators and binary literals. - consteval/constexpr/constinit. - std::move - std::forward - std::variant based mock pattern matching. - lambdas. - structured binding declarations. - an ABI for garbage collection. - coroutines. - concepts. C++20 even added a three-way comparison operator. This is just a random selection.
- spoiler 4y agoYou could build anything you desire with only a hammer if you're creative enough
- dureuill 4y agoYes, security researchers got quite a lot of work exploiting the antiquated tools of C coders [1]: https://www.opencve.io/cve?cwe=CWE-134 https://www.opencve.io/cve?cwe=CWE-134
- scrubs 4y agoget with the program. Printf in its 86 varieties has been around for years. Second, I'm increasingly associating that brit 70s show "keeping up appearances" with rust specifically that overly blushed face of the woman protagonist.
- deleted 4y ago[deleted]
- RcouF1uZ4gsC 4y agoActually, in defense of C++, there are not very many statically typed, compiled languages that can implement a type-safe print function that takes a variable number of arguments (that do not all share a base class) using the language itself. For many languages, print is special and the implementation is provided as part of the language implementation. ADDENDUM: Even iostreams back in the 1980's was a huge deal at the time, as it showed that it was possible to implement type-safe io in a statically typed, compiled language using language features just like any other library, instead of having it be special purpose.
- forrestthewoods 4y agohuh? Every new language has type safe print functions. Having to specify the type with printf formatters is archaic and awful.
- kccqzy 4y agoMost new languages with type-safe print functions are either dynamically typed (which makes this whole discussion moot) or leverage some significant advance in PL theory in order to make the print function type-safe. By advance, I mean not known before the 1990s.
- forrestthewoods 4y agoSorry, I explicitly meant statically typed languages. Rust, Nim, Zig, Odin, Jai, etc. > For many languages, print is special and the implementation is provided as part of the language implementation. Sure. Printing variables to strings is definitely worth language-level features imho. It's a bad thing if a language (C++) requires users to come up with extremely complex libraries (fmt/std::format) because the language lacks the features to make such a common operation simple and reliable. C style printf is a dumpster fire. C++ iostreams are unuseably slow. Modern languages definitely solve this particular problem much better!
- atq2119 4y ago
- deleted 4y ago[deleted]
- stevenhuang 4y agoThe format in C++20 is the 3rd party https://fmt.dev/latest/index.html https://fmt.dev/latest/index.html , which means even c++11 projects can use it.
- andrepd 4y agoRust always had printf. If you mean python-style fmt, there were also libraries for that for a long time, e.g. https://fmt.dev/ https://fmt.dev/
- kzrdude 4y agoafter 30 years, Python also finally has printf: world = "earth" print(f"Hello {world}")
- mk89 4y agoAnd it will take another 40 to convince people to use it instead of cout. :)
- renox 4y agoI doubt it very much: cout is so bad (ever tried to display an hexadecimal number? Barf!) that in many case printf is used instead even though it isn't typesafe. Which isn't so bad when you use a static checker in the CI, which you should anyway the C++ being so full of traps.
- taspeotis 4y agoHow's the Networking TS coming along? I can't wait to connect to ARPANET.
- deleted 4y ago[deleted]
- kybernetyk 4y ago[flagged]
- ridiculous_fish 4y agoHow do Rust projects localize their format strings?
- sedatk 4y agoThere's no built-in support for localization AFAIK.
- deleted 4y ago[deleted]
- tialaramex 4y agoFor a lot of Rust projects they just don't. Unlike a lot of stuff from the 1990s or so, the Rust core itself doesn't care about localization. It speaks Unicode fluently, but it doesn't try to think about locale problems at all. For projects that are user facing and so need to do localization, you do have a few options, including both gettext and ICU at opposite ends of the scale of "How difficult is your problem/ how much work do you want to do?" Gettext is just going to let you substitute translations like "X {1} Y {2}" in one language versus "YorX: {2}or{1}" in another language, whereas ICU understands how to render the Japanese form of today's date and which is the correct plural form for 38 of this thing in Russian (but it's still on you to prepare all the plural variants for each type of thing you want to quantify, ICU just tells you which of them to use)
- ridiculous_fish 4y agoThat's a depressing answer, that nobody bothers. gettext seems pretty difficult to use in Rust - there's no obvious way to apply a runtime format string "My name is {1}". https://blog.hackeriet.no/rust-and-translation-files/ https://blog.hackeriet.no/rust-and-translation-files/ describes using gettext from Rust but not how to handle format strings.
- haimez 4y agoIt seems reasonable to me that this is not a compile time constraint, but rather one of tooling and the deployed environment- ie: is the Japanese translation available here at runtime? How? Is there a runtime configuration flag? Is the translations file available at runtime? Is the date formatter configured appropriately? Maybe if you bake in assumptions about the deployed environment at compile time you can catch errors earlier for your specific environment, but all environments? No. That’s not the responsibility of the standard library and given the language lacks a single specific runtime target- definitely not the responsibility of the language/compiler. This is instead a classic use case for tests and opinionated (optional/third party) libraries instead.
- spoiler 4y agoIn the vector example, why not just `collect<...>()` and debug print that, or am I missing the point of what they tried to illustrate? Also, I do agree that C++'s approach to formatting the date was more user friendly/ergonomic, but on the other hand it's not something I've struggled with. I also feel like there is a more succinct version (I don't know if it would have the same performance characteristics and how much that mattered when writing the post). They also later mentioned overall consistency and coherence in the Rust ecosystem, and I value that more over a couple of hand crafted formatters in application code
- tialaramex 4y agoArguably we might not be able to store the resulting collection. For example suppose we've got 64MB of RAM, and we've got the iterator 0..i32::MAX If we try to collect() that, we're going to have a bad time, but while that's a lot of numbers, we certainly could write it to a file, we probably do have gigabytes of disk space, or we could send it over the network - the Internet certainly has storage for these numbers.
- estebank 4y agoBut even then the point still stands: you wouldn't want a non-custom debug representation of the iterator precisely because of the cost. You could create a new-type that wraps your iterator and implements a custom `Debug` impl with the contents you desire. Here the issue at play seems to be a disagreement on what is more useful in a Default implementation: a preview of the results of the iteration or a representation of the type structure. I believe that which of the two is more useful depends entirely on what you're using the Debug info for (with the caveat that going from the later to the former in your head is possible, but the other way around isn't).
- Arnavion 4y agoYou can't preview an arbitrary Iterator anyway because iterating is destructive. The elements you iterated will be gone forever. Besides, the typesystem won't let you do it anyway, since collect() requires either `Self` or `&mut Self`, and `fmt` only receives `&Self`. So at best you could only do this with Clone'able Iterators, but even then it might not be ideal for formatting to create "wasteful" clones. So maybe you'd only do it for individual Iterator types like Range or std::slice::Iter for which cloning is cheap. But even .map()ping such a trivially-Cloneable Iterator would yield a non-Cloneable or non-trivially-Cloneable Iterator, so at that point you have to wonder if it's worth making a small subset of Iterators have these "useful" Debug impls.