Is it just me, or does Rust feel much more bare-bones than other languages? I just started learning it recently and this is the one thing that stood out to me, much more so than the memory management business. A lot of things that would normally be part of the language has to be achieved through meta-programming in Rust.

Is this a deliberate design choice? What do we gain from this setup?

  • onlinepersona@programming.dev
    link
    fedilink
    English
    arrow-up
    7
    arrow-down
    1
    ·
    17 hours ago

    What are you talking about specifically? I’ve written C and C++ code and it’s terrible. UTF strings were an absolute pain, you needed Boost for the simplest things (though many things of boost have been assimilated in the standard), there are a thousand different ways to do simple things like iterating through iterables (again, things have changed in the standard), there’s no default dependency management, and so much more.

    Rust comes with dependency management, a way to write unit tests and integrations, generate docs, toggle features, has standard iterables, string handling, async, compiler targets, and a lot more things C and C++ could only dream of having.

    Don’t even get me started on zig which doesn’t have its own friggin string class / struct whatever.

    So again, I ask you, what are you referring to? Please provide examples.

    • cx40@programming.devOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      17 hours ago

      C++ was my first programming language. I remember the nightmare of dealing with dependencies and avoiding boost because it felt wrong to need a third part library for basic features. The toolchain for Rust is very nice (not just compared to C++, but all other languages I’ve worked with) and has so far been a huge joy to work with. The language itself too. I’m just curious about why the language likes to expose more of its features through meta-programming rather than directly in the language itself. Things like println! and format! being macros instead of functions, or needing a bunch of #[derive(Debug,Default,Eq,PartialEq)] everywhere for things that other language provide through regular code.

      • 6nk06@sh.itjust.works
        link
        fedilink
        arrow-up
        3
        ·
        edit-2
        14 hours ago

        Source needed because the Rust library is bigger than the STL, no one cares about print being a macro, and derivation being an issue only applies when you compare Rust to scripting languages.

        Rust is a contender to C and C++, not Visual Basic that can do everything poorly.

        A lot of things that would normally be part of the language

        Give examples because println is not convincing. Why would you have println on an embedded system that has no display?

        • cx40@programming.devOP
          link
          fedilink
          English
          arrow-up
          1
          ·
          12 hours ago

          I’m not saying that there’s a problem with doing things one way or another. Rather, I’m asking whether there’s a problem with doing things differently that then led to this design decision to be made with Rust. I want to better understand how this language came to be.

      • TehPers@beehaw.org
        link
        fedilink
        English
        arrow-up
        3
        ·
        14 hours ago

        Through macros? The term “meta-programming” had me lost since I’m only familiar with that in reference to C++ templates (and Rust’s generics are more like templates).

        println! and format! are macros because they use custom syntaxes and can reference local variables in a string literal provided to the macro:

        let a = 2;
        println!("{a:?} {b}", b=a);
        

        I don’t know how the derive macros would be function calls. They generate whole impls.

        Macros generate new code. This is the same idea as C macros (except Rust macros generate syntax trees, not tokens, but that’s a minor difference).

        So to answer your question as to why there are macros, it’s because you need to generate code based on the input. A function call can’t do that.

        • cx40@programming.devOP
          link
          fedilink
          English
          arrow-up
          1
          ·
          12 hours ago

          The term “meta-programming” had me lost since I’m only familiar with that in reference to C++ templates (and Rust’s generics are more like templates).

          Yes, like C++ template and macros. The kind of code that generates new code before being run.

          So to answer your question as to why there are macros, it’s because you need to generate code based on the input. A function call can’t do that.

          You can design a language where you don’t need to generate code to accomplish this. My question isn’t why this is necessary in Rust. My question is why Rust was designed such that this was necessary.

          Someone mentioned elsewhere that this allows for compile-time type safety. I’m still trying to wrap my head around how that works.

          • anton@lemmy.blahaj.zone
            link
            fedilink
            arrow-up
            1
            ·
            6 hours ago

            You can design a language where you don’t need to generate code to accomplish this.

            Other people have python scripts generate C, so having on in the same codebase and language is certainly an improvement.

            My question isn’t why this is necessary in Rust. My question is why Rust was designed such that this was necessary.

            Because otherwise the compiler team either also needs to maintain a huge amount libraries or cut corners and move things to runtime that really should happen at compile time.

            Someone mentioned elsewhere that this allows for compile-time type safety. I’m still trying to wrap my head around how that works.

            printf is a great example. According to the C type system, it takes a string and a variable amount of untyped arguments dependent on the content of the string, but that doesn’t actually describe the allowed arguments.
            Misusing printf like this printf("%s", 42); will get you a warning, but only because there is a special case for printf in the compiler. If you have your own function that does the same as printf, and you misuse the same way, you will find out by dissecting the core dump.

            In rust the format string gets parsed at compile time by a macro, which decides the type of each arguments that can than be checked by the compiler. Imagine printf(“%d %d”,…) created a function with the signature specialized_printf(char* agr0, int arg1), it would be impossible to pass the wrong types of arguments.

            Now that these tools exist people have gone further and made a library that checks SQL queries against the shema of a running database and causes a compile error if it doesn’t fit.

          • TehPers@beehaw.org
            link
            fedilink
            English
            arrow-up
            1
            ·
            12 hours ago

            You can design a language where you don’t need to generate code to accomplish this.

            Depending on what you mean by “generate code”, the only language at the level of C or C++ that I can think of that does this is Zig. Zig is weird though because you’re still doing what is functionally compile-time reflection, so in a way you’re still generating code, just in a different way.

            If you’re comparing to Python, JS, or even C#, those all come with runtimes that can compile/interpret new code at runtime. None of those languages are comparable here. Rust, C, C++, Zig, etc compile into assembly, and type information, impl information, etc are all lost after compilation (ignoring symbol names or anything tracked as debug info).

            If you’re specifically referring to Debug, Display, PartialEq, etc then the compiler doesn’t do that for you because Rust doesn’t assume that those traits are valid for everything.

            Unlike Java where new Integer(1) != new Integer(1) or JS where "" == 0, Rust requires you to specify when equality comparisons can be made, and requires you to write out the implementation (or use the derive for a simple, common implementation).

            Unlike C# where record class Secret(String Value); will print out the secret into your logs when it inevitably gets logged, Rust requires you to specify when a type can be formatted into a string, and how it should be formatted.

            Just because a language does things one way doesn’t mean every language ever should do things that same way. If you want it to work like another language you like to use, use the language you like to use instead. Rust language designers made explicit decisions to not be the same as other languages because they wanted to solve problems they had with those languages. Those other languages are still usable though, and many solved the same problems in other ways (C#'s nullable reference types, Python’s type hints, TypeScript, C++'s concepts, etc).