• 0 Posts
  • 124 Comments
Joined 1 year ago
cake
Cake day: June 21st, 2023

help-circle
  • Adding a single unused function should no effect on runtime performance. The compiler removes dead code during compilation, and there’s no concept at runtime anyway of “creating a function” since it’s just a compile-time construct to group reusable code (generally speaking - yes the pedants will be right when they say functions appear in the compiled output, to some extent).

    Anyway, this can all be tested on Godbolt anyway if you want to verify yourself. Make a function with and without a nested unused function and check the output.



  • Pushing HTML even further, one could say it’s a declarative programming language that programs a UI in a mostly-stateless manner (inputs aren’t really stateless but you can argue the state is provided by the UI rather than managed by HTML).

    I’m not sure I’d make this leap myself though, I have a hard time classifying it (or any other markup language) as a PL. As far as I am aware, you can’t really program a state machine with pure HTML, though you can accept inputs and return outputs at least.


  • Two thoughts come to mind for me:

    1. I think people should feel free to use any language however they want for their own needs and projects, but it’s also important to understand what exactly “unsound” and “undefined behavior” mean if you’re going to dabble with them. If it’s a risk you’re willing to take, go for it, but don’t be surprised if things break in ways that make no sense at all. Realistically a compiler won’t delete your root directory if you trigger UB or anything, but subtle bugs can creep in and change behaviors in ways that still run but which make unrelated code break in difficult to debug ways.
    2. The borrow checker is one of Rust’s biggest features, so looking for ways around it feels a bit counterproductive. Feature-wise, Rust has a lot of cool constructs around traits and enums and such, but the language and its libraries are built around the assumption that the guarantees the compiler enforces in safe code will always be true. These guarantees extend beyond the borrow checker to things like string representation and thread safety as well. As an alternative, some other languages (like C++, which you mentioned, or maybe even Zig) might be better suited for this approach to “dirty-but-works” development, and especially with C++, there are some excellent tools and libraries available for game development.



  • I think it’s good to document why things are done, but extracting things out into another function is just documenting what is being done with extra steps. This also comes with a number of problems:

    1. Not all languages are readable. Documenting what is being done is important in some C, or when working with some libraries that have confusing usage syntax.
    2. Not all people reading the code know the language or libraries well. Those people need guidance to understand what the code is trying to do. Function names can of course do this, but…
    3. Not all types can be named in all languages. Some languages have a concept of “opaque types”, which explicitly have no name. If parameter and return types must be specified in that language, working around that restriction may result in unnecessarily complicated code.
    4. Longer files (the result of having dozens of single-use functions) are less readable. Related logic is now detached into pointers that go all over the file all because of an allergic reaction to code comments, where a simple // or # would have made the code just as readable.
    5. Function names can be just as outdated as code comments. Both require upkeep. Speaking from personal experience, I’ve seen some truly misleading/incorrect function names.



  • Still working on an assertions library that I started a few weeks ago. I finally managed to get async assertions working:

    expect!(foo(), when_ready, all, not, to_equal(0)).await;
    

    It also captures values passed down the assertion chain and reports them on failure (without requiring all types to implement Debug since it uses autoref specialization).

    Hopefully it’ll be ready for a release soon.



  • For library code - yes, you’d usually want to direct users to the correct way of using the library, so you’d be more likely to come across fallible build functions or a bunch of type parameters to constrain when it can be called.

    For applications - honestly, it’s your code, so do what makes sense to you. Using a build function can help you ensure your settings were fully configured before using them, but it’s up to you if that’s the direction that makes the most sense to you. One benefit is you only need to perform the check once, but the downside is having another “built” type that you need to keep in sync with the original type. You can also look at libraries like derive_builder if you want to have your builder generated for you to avoid needing to manually update two separate types.






  • Anytime anyone mentions integrating an HTTP client into Rust’s std, all it takes is one good Python anecdote to shut that discussion right down.

    Having the standard library be stable and not try to add a bunch of support for changing standards is a long-term benefit to the language. Having “de-facto standard libs” with crates like url, http, etc ends up being better because they can evolve independently from the standard library, at the pace their respective domains evolve.

    Although, I suppose an argument could be made that url is unlikely to really evolve anymore.


  • Ignoring the rest, just some thoughts about the list of proposed features:

    A capture trait for automatic cheap clones

    Automatic implicit cloning would be useful for high level developers, but not ideal at all for low level or performance-sensitive code. It’s not the case that anyone using a shared pointer wants to clone it all the time. The high level usecase doesn’t justify the cost assumed by the low level users.

    Instead, being able to wrap those types with some kind of custom “clone automatically” type feels like a middle ground. It could be a trait like mentioned, or a special type in the standard library. Suppose we call it Autoclone[T] or something (using brackets because Lemmy nonsense). Autoclone[Rc[T]] could function like the article mentioned.

    Automatic partial borrows for private methods

    Having “private” and non-“private” methods function differently feels like confusing behavior that should be avoided if possible. Also, “private” I assume refers to pub(self) methods (the default if unspecified), which is “module-level” methods (so accessible within the module it’s defined in). Anyway, there are years of discussion around this so I’ll just defer to that as to why it’s not in yet.

    I agree with the urge to make it happen though. Some method of doing partial borrows for methods would be nice.

    Named and optional function parameters

    This is what prompted me to even comment. What “every language” does for complex constructors is different per language. C#, for example, supports both named and optional parameters, but construction usually uses an object initializer:

    var jake = new Person("Jake")
    {
        Age = 30,
        // ...
    };
    

    This is similar to Rust’s initializers:

    let jake = Person {
        age: 30,
        ...Person::new("Jake")
    };
    

    Where it gets tricky is around required parameters. Optional ones don’t really matter since you can use the syntax above if you want, or chain methods like with the builder style.

    As for the overhead of writing builders, there’s already libraries that let you slap #[derive(Builder)] on types and get a builder type automatically.

    As for optional parameters, how those are implemented differs between languages. In C#, default values must be constant values. In Python, default values are basically “global” values and this nonsense is possible:

    def count_calls(count=[]):
        # if unset, count is a global list
        count.push(0)
        return len(count)
    

    Anyway, all this is to say that the value of optional parameters isn’t obvious.

    Named parameters is more of a personal choice thing, but falls apart when your parameter has no name and is actually a pattern:

    async fn get_foo(_: u32) {}
    

    Also, traits often use names prefixed with underscores in their default fn impls to indicate a parameter an implementer has access to, but the trait doesn’t use by default. Do you use that name, or the name the implementer defined? I assume the former since you don’t always know the concrete type.

    Faster unwrap syntax

    We have that, it’s called the try operator.

    Okay I know it’s different, and I know everyone’s use case is different, but I’ve been coding long enough to know that enabling easy unwraps means people will use it everywhere despite proper error handling being pretty dang important in a production environment.

    Thinking of my coworkers alone, if we were to start writing Rust, they’d use that operator everywhere because that’s what they’re familiar with coming from other languages. Then comes the inevitable “how do I add a try-catch block?” caused by later needing to handle an error.

    Anyway, I prefer the extra syntax since it guides devs away from using that method over propagating the error upwards. For the most part, you can just use anyhow::Result and get most error types converted automatically.

    Try trait

    Yes please.

    Specialization

    Yes please.

    Stabilizing async read/write traits to standardize on an executor API

    I’d want input from runtime devs on this, but if possible, yes please.

    Allowing compilation of builds that fail typechecking

    ???

    How is the compiler going to know how to compile the code if it doesn’t know the types? This isn’t Python. The compiler needs to know things like how much memory to allocate, and there’s a ton of potential unsound behavior that can occur from treating one type as another, even if they’re the same size.

    Anyway I’ll save the rest for later since I’m out of time.


  • Speaking as someone with a MTF close friend and NB spouse, but the term used in the article is the term everyone around me used when I was growing up. That term may be obsolete now, and if so, the author simply needs to be informed. There’s no need to assume they meant harm by it.

    If they knowingly used a term that may offend, then that’s of course a separate issue.