• 0 Posts
  • 217 Comments
Joined 3 years ago
cake
Cake day: June 21st, 2023

help-circle
  • Breaking down what async fn in trait does, it converts it to a fn method() -> impl Future<Output=T>, which breaks further down into fn method() -> Self::__MethodRet where the generated associated type implements Future.

    This doesn’t work for dyn Trait because of the associated type (and the fact method() needs a dyn-safe self parameter).

    Instead, your methods need to return dyn Future in some capacity since that return type doesn’t rely on associated types. That’s where async_trait comes in. Box<dyn Future<...>> is a dyn-safe boxed future, then it’s pinned because async usually generates a self-referential type, and you need to pin it anyway to .poll() the resulting future.

    Edit: also, being pedantic, associated types are fine in dyn traits, but you need to specify the type for it (like dyn Blah<Foo=i32>. Even if you could name the return type from an async fn, it’d be different for every impl block, so that’s not realistic here.


  • While I agree with your post, I do want to call out that Rust’s standard library does use a lot of unstable features and calls compiler intrinsics. Anyone can use the unstable features I believe with just #![feature(...)], but not the intrinsics (not that there’s much reason to call the intrinsics directly anyway).


  • TehPers@beehaw.orgtoRust@programming.devWhy is Rust so bare-bones?
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    3 days 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).


  • Part of why Python can do this is that it runs completely differently from Rust. Python is interpreted and can run completely arbitrary code at runtime. It’s possible to exec arbitrary Python.

    Rust is compiled ahead of time. Once compiled, aside from inspecting how the output looks and what symbol names it uses, there’s nothing that ties the output to Rust. At runtime, there is nothing to compile new arbitrary code, and compiling at runtime would be slow anyway. There is no interpreter built into the application or required to run it either.

    This is also why C, C++, and many other compiled languages can’t execute new arbitrary code at runtime.


  • 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.




  • If you already know some programming languages, look for some kind of GUI or game library for it to see if you can use it. If not, something like Blender might be easiest to make in C++, Rust, C (if you’re a masochist), or maybe Zig. This may also influence the shading language you choose. Start with this.

    You will need to know some shader language. You have a few options there, but the most popular are GLSL and OpenGL (though I’d prefer GLSL). There’s also WGSL and some others, but they aren’t as popular. Prefer whatever the graphics library you’re using wants you to use.

    Math is very heavy on linear algebra. Look up PBR if you want to render realistic 3d shapes. Google’s Filament is well documented and walks through implementing it yourself if you want, but it’s pretty advanced, so you might want to start simpler (fragment colors can just be base color * light color * light attenuation * (N*L) for example).








  • TLDR: data is something you collect over time from users, so you shouldn’t let the contracts for it mindlessly drift, or you might render old data unusable. Keeping those contracts in one place helps keep them organized.


    But that explanation sucks if you’re actually five, so I asked ChatGPT to do that explanation for you since that would be hard for me to do:

    Here’s a super-simple, “explain it like I’m 5” version of what that idea is trying to say:

    🧠 Imagine your toys

    You have a bunch of toys in your room — cars, blocks, stuffed animals.

    Now imagine this:

    • You put some cars in the toybox.

    • You leave other cars on the floor in another place.

    • You keep some blocks in a bucket… and some blocks on the shelf.

    • And every time you want a toy, you have to run to a different spot to find its matching pieces.

    That would be really confusing and hard to play with, right? Because things are spread out in too many places for no good reason.

    🚧 What the blog is really warning about

    In software (computer programs), “state” is like where toys are stored — it’s important information the program keeps track of. For example, it could be “what level I’m on in a game” or “what’s in my cart when I shop online.”

    The article says the biggest mistake in software architecture is:

    Moving that important stuff around too much or putting it in too many places when you don’t need to.

    That makes the program really hard to understand and work with, just like your toys would be if they were scattered all over the place. (programming.dev)

    🎯 Why that matters

    If the important stuff is all over the place:

    • People get confused.

    • It’s harder to fix mistakes.

    • The program gets slower and more complicated for no reason.

    So the lesson is:

    👉 Keep the important information in simple, predictable places, and don’t spread it around unless you really need to. (programming.dev)



  • which has existed for much longer than has crates.io

    The Rust compiler has not existed for as long as the debian package manager has. You’re still trusting it and its standard library even if your reason for trusting it is that Debian’s maintainers trust it. This is also true of any vetted dependencies you download. You’re trusting others at the end of the day, whether they are the package developers or the auditors. Only by auditing your dependencies yourself can you avoid trusting anyone else.

    With that being said, you are also able to contribute here. Someone has to do the auditing. Go audit some packages!



  • The never type comes more from type theory and isn’t common in other languages (though TS has never). Similar to 0 or the null set, it exists as a “base case” for types. For example, where you have unions of T1 | T2 | ..., the “empty union” is the never type. Similarly, for set theory, a union of no sets is the null set, and in algebra, the summation of no numbers is 0.

    In practice, because it can’t be constructed, it can be used in unique ways. These properties happen to be super useful in niche places.