7 ms·
How would Rust help here? Isn't it famous for having too many string types?
by mseepgood 4y ago
How would Rust help here? Isn't it famous for having too many string types?
- _nalply 4y agoIt's a complicated but well-thought out system which tends to avoid copies by making them explicit in the source code and preferring taking references or slices which are cheap operations. The string slice for example is an Unicode-capable view into bytes of the string (immutably pre-compiled static bytes in the binary, bytes of fixed length on the stack or heap-allocated). The aliasing rules are enforced by the compiler, so it is safe to throw around pointers and sizes and not to worry about buffer overflows, as long as it compiles.
- cowmoo728 4y agoIn C++ when I see `DoX(y)` I have to worry every time about temporary lifetimes, copy vs move operator, and a bunch of other things that are easy to miss during code review. It is so easy to accidentally copy large strings around many times in a performance critical loop. Rust makes all of that easier to see during code review. It is very explicit about these things. I'm a Google employee working on chromium and chromeOS and have been asking internally about rust support for over a year now, so it's exciting that it's making progress.
- imron 4y agoIt's the 'too many string types' that helps. With C++, if you have char*'s (because you don't need to own the memory) and you pass it to a function that takes a const std::string& (because it also doesn't want to own the memory), then there will still be an implicit conversion to a temporary std::string (involving an allocation) despite neither the caller or the callee needing to own any memory. With Rust, if you have a &str (because you don't need to own the memory) and you pass it to any function that takes a String (or even the unidiomatic &String), then you will get a compile error. There won't be any implicit conversion of types and therefore no implicit allocation. If you really want to pass it, you need to explicitly convert it, making the cost of the allocation explicit. Rust's "too many strings" model says "there are many different ways in which you can use string-like objects, each with their own performance tradeoffs. Know which one you want to use in your code or I won't compile".
- flohofwoe 4y agoIt would most likely suffer from similar problems when interacting with the C and C++ APIs in the rest of Chrome though (e.g. what to do if you have a Rust String, but the other side wants a const ref to a C++ std::string).
- imron 4y agoUse a CxxString: https://cxx.rs/binding/cxxstring.html https://cxx.rs/binding/cxxstring.html At some point there will need to be an allocation when crossing Rust -> C++ boundary because Rust strings are not null-terminated. The difference Rust makes is that unlike C++, it is always explicit when the allocation occurs.
- IshKebab 4y agoUnfortunately the restrictions mentioned on that page make it quite a pain to use in practice.
- hoseja 4y agopsst... std::string_view
- imron 4y agoA step in the right direction if you have a compiler with c++17 support. Note: chrome only supported c++17 features in Dec 2021 [0], and whether std::string_view is allowed to be used is still 'to be determined'. 0: https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++-features.md https://chromium.googlesource.com/chromium/src/+/HEAD/styleg...
- cmrdporcupine 4y agoA variant of what is becoming string_view in the standard has existed within Google's codebase(s) for years. I don't recall using it much in Chromium when I worked in there, but it's all over Google3 and is now in absl (Google's open sourcing of some of its base c++ components). Chromium has "string_piece": https://chromium.googlesource.com/chromium/src/base/+/refs/heads/main/strings/string_piece.h https://chromium.googlesource.com/chromium/src/base/+/refs/h... which is at least 9 years old (was moved from elsewhere in the repo into base/ then)
- tialaramex 4y agoA mixture of culture and technology. Technologically, Rust's only built-in string type, &str, is a reference to a string slice - that is, you can't change it (the reference isn't mutable) and it is both a pointer to the start of some UTF-8 and the length of the UTF-8. What encoding? Always UTF-8. Only UTF-8. Not "Well, it's kinda UTF-8 but..." it's just always UTF-8. This moves the burden to a single place, your text decoding code, to do things correctly, and great news - the entire world is moving to UTF-8, so you're on a downhill gradient where every week this works better without you lifting a finger. That reference knowing the length is brilliant. Trimming whitespace off a string? You can just make another immutable reference to the smaller trimmed string. Zero copies. Slicing a URL up into components? You can do that too, zero copies. And yet it's all memory safe. Now, Chromium is not some raw firmware for a $1 micro-controller, so it has library types like Rust's alloc::string::String (you can just name it "String" in normal Rust code but that is its full name) which, as its presence in alloc suggests, is an allocating String type, you can concatenate them, you can make them by formatting a bunch of other variables, the default ones are empty, the data goes on your heap and so on. But, String is AsRef<str> which means if what you've got is a String, and what you're doing is calling a function that wants &str Rust is OK with that and it costs nothing at runtime. Why? Because that &str is just two of the elements of the String type you had, the pointer into the heap and the length, it's easy. Rust has lots of other types for stuff like Foreign Interfaces, like CStr and CString (for the C-style NUL-terminated array of bytes which might be text) but your pure Rust code shouldn't care about those, often it can say (unsafely) "Look, the C++ promises this is UTF-8, we'll take their word for it" or "I only need it to have bytes in it, let's make [u8] and we're done". Culturally, Rust programmers write &str when that would do. There's a strong cultural pressure not to write String when you really mean &str, and the compiler won't let you write &str if you needed String. So this results in less thunking of the sort complained about in C++
- vlovich123 4y agoEveryone’s trying to justify the response when the honest truth is that no, Rust doesn’t solve the problem of abstraction layer impedance mismatches causing ownership to be dropped only to be reacquired at the next level. On a sufficiently large/complicated code base, the problem will arise. As others have mentioned, various kinds of string types are baked into the language which makes it ergonomic to do “the right thing” from the get go, but hard to say. I would be skeptical of claims that it would make a difference, especially in the interim where you now have an added impedance mismatch with C++, Rust, C.
- tialaramex 4y ago> Rust doesn’t solve the problem of abstraction layer impedance mismatches causing ownership to be dropped only to be reacquired at the next level The expression of what C++ does here in Rust is awkward and, I think, nobody has proposed it because you'd never write that. Basically C++ char* is a raw pointer. Rust does have those, but you'd never use them in this context. What you would use is either the borrowed slice reference &str or the owning String type, but in both cases we have an owned object and there's our crucial difference. If you've got the owned String, and I needed an owned String, I should ask for your owned String, and we're done. In C++ "dropping" ownership as you describe is no big deal, the C++ design doesn't care, but in Rust if you actually drop(foo) it's gone. The references to it can't out-live that, if it's gone then they're gone. If you write code that gives away references and then tries to drop the thing they're references to, Rust will object that this is nonsense, because it is nonsense, you need to ensure those references are gone before dropping the thing they refer to. As a result I feel you're greatly under-estimating the ergonomic difference.
- vlovich123 4y ago> In C++ "dropping" ownership as you describe is no big deal, the C++ design doesn't care, but in Rust if you actually drop(foo) it's gone I think you’ve built a straw man of my argument and then argued with that. Clearly I meant that it seems possible that a sufficient complicated call stack could still be set up to jump between needing the owned String type and the borrowed &str type. That’s what I meant by dropping ownership as that’s what’s happening in the c++ code when you go between char*/string (the API is dropping its need for ownership). The argument of “ If you've got the owned String, and I needed an owned String, I should ask for your owned String, and we're done” is weak because that same argument would apply to C++ code and yet the code still ended up that way when you pasted together components in a very large code base. Now maybe it’s a bit simpler because you have string, string&, const string&, and const char* and doing that antipattern that happened in C++ just wouldn’t be ergonomic in Rust. Maybe. But that feels like a very thin argument and not “this is impossible in Rust”.