1. 21
  1.  

  2. 9

    I agree the String thing is confusing, in fact the author didn’t list quite a few different string types that exist, and listed no where near the amount of string conversions or ways to get string-y arguments. However, it’s one of those cases where the underlying problem Rust solved with this confusion actually exists in (nearly?) all languages. Rust, in typical Rust fashion just makes you aware of all the footguns up front. Once you wrap your head around when to use each type, or what their various tradeoffs are, it makes perfect sense. So much so that I’ll get frustrated with other languages that paper over these details leading to bugs. Bottom line, strings in general are hard, really hard.

    1. 3

      I think the distinctions that Rust make are useful and necessary. However, I think one of the problems is that the types are confusingly named. I think String should have been called StringBuf and OsString OsStringBuf, just as you have Path and PathBuf`.

      I think an additional problem that make slices ([T]) and string slices (str) difficult to understand is that they are unsized, built-in types. So, people have to understand the difference between e.g. &str and str and why you cannot just put str in e.g. a struct. I know that there are good reasons for why string references are as they are, but I think from a learning curve perspective, it would have been easier if string slices were something along the lines of a simple Copyable type:

      struct StringSlice<'a> {
        buf: &'a StringBuf,
        lower: usize,
        upper: usize,
      }
      
      1. 1

        Having references to unsized slices is necessary to avoid a performance hit. The StringSlice type above is 1 word larger than &str (which is just a pointer and a length). More importantly it has an additional layer of indirection: buf points to the StringBuf which points to the data, while &str points directly at the relevant data.

        1. 2

          You don’t have to convince me. Like I said, there are good reasons for the current representation. It’s just that it makes (string) slices more opaque.

          This is also quite opposite to many other types Rust, which are transparent and can be understood by just reading the standard library.

          One of my favorite examples is the BinaryHeap::peek_mut method, which would be completely unsafe in another language (since you can modify the tip of the heap, which invalidates the heap property), but in Rust it can be done without any magic. The borrow system takes care of that you can only have one mutable reference (so no-one else can have a view of the heap when the heap property is temporarily broken), the Drop implementation of PeekMut takes care of restoring the heap property when necessary.

      1. 2

        No, this is awesome to know! This seems to be a link aggregator after all. Plus, the point of the blog post was just to list some gripes about a language that the author really loves.

      2. 3

        The string zoo is confusing, but they all exist for a good reason. Once you know them, it’s actually helpful.

        • Without a GC there has to be a distinction between owned and borrowed strings (roughly equivalent of mallocated and “const” strings in C). If it was one type, you’d have to ask each time “do I free this or not?”. Rust gave them separate types, so you don’t have to ask, you can’t cause double-free errors, and the compiler can even insert free() for you.

        • Rust has chosen to have UTF-8 strings. File names, env variables, and lots of OS-specific strings aren’t guaranteed to be UTF-8, so they get a different type, so that you can losslessly roundtrip them and don’t make mojibake.

        • Rust cares about memory overhead, performance, and predictability — pick all three. Some languages choose to have one “clever” string type that has all kinds of optimizations based on heuristics and assumptions, but these aren’t always predictable or optimal. Rust instead tells you to pick the kind of string that works best for you (growable or fixed-size, optimized for small or large, etc.), and various AsRef interfaces help make custom string types work like normal strings.

        1. 1

          This is just like saying python have so many methods to format strings. However I think rust case is better since they perform similarly but python formatting have different performance with different methods.

          1. 2

            Also, the many conversion functions are a side-effect of the many useful conversion traits that Rust provides, e.g. From, Into (implemented automatically for ‘counterpart’ types for which From is implemented), and ToOwned.

            From and ToOwned just happened to apply both to slices and string slices. (In fact, From<&str> for String is even implemented using ToOwned.)

          2. 1

            Multiple string types and relevant traits beget multiple conversion functions:

            I did miss Cows in the string types comparison too and although I agree with the sibling comment that Rust just shows you footguns that are really there I agree that this can be a little bit confusing. String types, and in general references seriously impact how one designs the API so this is not a trivial matter.

            Sometimes rustc needs me to add where Self: Sized to my static (…)

            I believe it has something to do with object safety. This issue actually uncovers one thing that people keep talking about: no written standard for Rust exists.

            I actually enjoy reading books about languages but the existing Rust books just scratch the surface of Rust as language. I think I read all available books on Rust on the internet but only one got deep enough to cover differences between Fn, FnOnce, FnMut and using more specialized std APIs like PhantomData.

            1. 3

              I actually enjoy reading books about languages but the existing Rust books just scratch the surface of Rust as language. I think I read all available books on Rust on the internet but only one got deep enough to cover differences between Fn, FnOnce, FnMut and using more specialized std APIs like PhantomData.

              I really liked Programming Rust by Jim Blandy and Jason Orendorff. It also covers Fn* and PhantomData.

              1. 1

                Yes, that’s the book I’ve been referring to :) And I liked it very much too. I’m glad that Jim is writing 2nd edition now and I’ll blindly buy it and read it. Even though I’ve overgrown the initial pains the writing style is very nice and the last chapter about integration with libgit2 was really impressive.

                I had high hopes for the Rust Programming Language book thinking it would be more like a spec covering everything but unfortunately some advanced concepts were just briefly mentioned.

                1. 2

                  That’s how I felt with the book too, I ended up having to ask a bunch of questions of my friends of concepts that aren’t in the book… I should give Programming Rust a try

                  1. 1

                    Depending on your journey you may already know a lot of what’s in Programming Rust but thinking from a longer perspective that’s actually that one book which I’d recommend reading about Rust. (Rust in Action has nice fragments esp building a kernel but it’s still not finished and not exhaustive enough).

                    Maybe that’s just me but it seems like most Rust book authors think that Rust is complex but people want a “for dummies” book and spend a lot of time with borrow checker and syntax trivia instead of proper in depth treatment. I’d kill for a proper Advanced Rust book instead of collecting random knowledge across the internet.

                  2. 1

                    And I liked it very much too. I’m glad that Jim is writing 2nd edition now and I’ll blindly buy it and read it.

                    Same here! I was really happy to see that they are keeping the book in sync with recent Rust changes.