| ▲ | bfrog 16 hours ago |
| One example where Rust enables better and faster abstractions is traits. C you can do this with some ugly methods like macros and such but in Rust it’s not the implementers choice it’s the callers choice whether to use dynamic dispatch (function pointer table in C) or static dispatch (direct function calls!) In c the caller isn’t choosing typically. The author of some library or api decides this for you. This turns out to be fairly significant in something like an embedded context where function pointers kill icache and rob cycles jumping through hoops. Say you want to bit bang a bus protocol using GPIO, in C with function pointers this adds maybe non trivial overhead and your abstraction is no longer (never was) free. Traits let the caller decide to monomorphize that code and get effectively register reads and writes inlined while still having an abstract interface to GPIO. This is excellent! |
|
| ▲ | emidln 16 hours ago | parent | next [-] |
| I probably enjoy ELF hacking more than most, but patching an ELF binary via LD_PRELOAD, linker hacks, or even manual or assisted relinking tricks are just tools in the bag of performant C/C++ (and probably Rust too, but I don't get paid to make that fast). If you care about perf and for whatever reason are using someone else's code, you should be intimately familiar with your linker, binary format, ABI, and OS in addition to your hardware. It's all bytes in the end, and these abstractions are pliable with standard tooling. I'd usually rather have a nice language-level interface for customizing implementation, but ELF and Linux scripting is typically good enough. Binary patching is in a much easier to use place these days with good free tooling and plenty of (admittedly exploit-oriented) tutorials to extrapolate from as examples. |
|
| ▲ | K0nserv 16 hours ago | parent | prev | next [-] |
| > In c the callers isn’t choosing typically. The author of some library or api decides this for you. Tbf this applies to Rust too. If the author writes fn foo(bar: Box<dyn BarTrait>)
they have forced the caller into dynamic dispatch.Had they written fn foo(bar: impl BarTrait)
the choice would've remained open to the caller |
| |
| ▲ | nicoburns 16 hours ago | parent [-] | | Right, but almost all APIs in Rust use something like fn foo(bar: impl BarTrait)
and AFAIK it isn't possible to write that in C (though C++ does allow this kind of thing). | | |
| ▲ | bfrog 14 hours ago | parent | next [-] | | C++ you either use templates or classes and virtuals. In either case the caller doesn't get to decide. | | |
| ▲ | Seattle3503 3 hours ago | parent [-] | | Interesting, there isn't some way to have a template that is polymorphic over virtuals? |
| |
| ▲ | bsaul 14 hours ago | parent | prev [-] | | how do apis typically manage to actually « use » the « bar » of your example, such as storing it somewhere, without enforcing some kind of constraints ? | | |
| ▲ | steveklabnik 14 hours ago | parent | next [-] | | "BarTrait" is the constraint. This is monomorphized for every type you pass in, in short. | |
| ▲ | Maxatar 11 hours ago | parent | prev [-] | | If you need to store the value then you have no choice but to take in a dyn trait. | | |
| ▲ | steveklabnik 11 hours ago | parent | next [-] | | Depending on exactly what you mean, this isn't correct. This syntax is the same as <T: BarTrait>, and you can store that T in any other generic struct that's parametrized by BarTrait, for example. | | |
| ▲ | marcosdumay 7 hours ago | parent [-] | | > you can store that T in any other generic struct that's parametrized by BarTrait, for example Not really. You can store it on any struct that specializes to the same type of the value you received. If you get a pre-built struct from somewhere and try to store it there, your code won't compile. |
| |
| ▲ | tcfhgj 11 hours ago | parent | prev [-] | | sure about that? the struct in which it is stored, could be generic as well | | |
| ▲ | Maxatar 10 hours ago | parent [-] | | I'm addressing the intent of the original question. No one would ask this question in the case where the struct is generic over a type parameter bounded by the trait, since such a design can only store a homogeneous collection of values of a single concrete type implementing the trait; the question doesn't even make sense in that situation. The question only arises for a struct that must store a heterogeneous collection of values with different concrete types implementing the trait, in which case a trait object (dyn Trait) is required. |
|
|
|
|
|
|
| ▲ | embedding-shape 16 hours ago | parent | prev | next [-] |
| It's a tradeoff though, as I think traits makes the Rust build times grow really quickly. I don't know the exact characteristics of it, also I think they speed it up compared to how it used to be, but I do remember that you'll get noticeable build slowdowns the more you use traits, especially "complicated" ones. |
| |
| ▲ | treyd 16 hours ago | parent | next [-] | | Code is typically run many more times than it's compiled, so this is a perfectly good tradeoff to make. | | |
| ▲ | embedding-shape 16 hours ago | parent | next [-] | | Absolutely, was not trying to claim otherwise. But since we're engineers (at least I like to see myself as one), it's worth always keeping in mind that almost everything comes with tradeoffs, even traits :) Someone down the line might be wondering why suddenly their Rust builds take 4x the time after merging something, and just maybe remembering this offhand comment will make them find the issue faster :) | |
| ▲ | cardanome 16 hours ago | parent | prev [-] | | For release builds yes. For debug builds slow compile times kill productivity. | | |
| ▲ | greener_grass 16 hours ago | parent | next [-] | | If you are not willing to make this trade then how much of a priority was run-time performance, really? | | |
| ▲ | esrauch 16 hours ago | parent | next [-] | | It's never the case that only one thing is important. In the extreme, you surely wouldn't accept a 1 day or even 1 week build time for example? It seems like that could be possible and not hypothetical for a 1 week build since a system could fuzz over candidate compilation, and run load tests and do PGO and deliver something better. But even if runtime performance was so important that you had such a system, it's obvious you wouldn't ever have developer cycles that take a week to compile. Build time also even does matter for release: if you have a critical bug in production and need to ship the fix, a 1 hour build time can still lose you a lot here. Release build time doesn't matter until it does. | |
| ▲ | 16 hours ago | parent | prev [-] | | [deleted] |
| |
| ▲ | torginus 15 hours ago | parent | prev | next [-] | | A lot of C++ devs advocate for simple replacements for the STL that do not rely too much on zero-cost abstractions. That way you can have small binaries, fast compiles, and make a fast-debug kinda build where you only turn on a few optimizations. That way you can get most of the speed of the Release version, with a fairly good chance of getting usable debug info. A huge issue with C++ debug builds is the resulting executables are unusably slow, because the zero-cost abstractions are not zero cost in debug builds. | | |
| ▲ | pjmlp 14 hours ago | parent [-] | | Unless one uses VC++, which can debug release builds. Similar capabilities could be made available in other compilers. | | |
| ▲ | torginus 10 hours ago | parent | next [-] | | Its not just the compiler - MSVC like all others has a tendency to mangle code in release builds to such an extent that the debug info is next to useless (which to be fair is what I asked it to do, not that I fault it). Now to hate a bit on MSVC - its Edit & Continue functionality makes debug builds unbearably slow, but at least it doesn't work, so my first thing is to turn that thing off. | | | |
| ▲ | cyberax an hour ago | parent | prev [-] | | You can debug release builds with gcc/clang just fine. They don't generate debug information by default, but you can always request it ("-O3 -g" is a perfectly fine combination of flags). |
|
| |
| ▲ | arw0n 14 hours ago | parent | prev | next [-] | | I think this also massively depends on your domain, familiarity with the code base and style of programming. I've changed my approach significantly over time on how I debug (probably in part due to Rusts slower compile times), and usually get away with 2-3 compiles to fix a bug, but spend more time reasoning about the code. | |
| ▲ | kace91 16 hours ago | parent | prev | next [-] | | Doesn’t rust have incremental builds to speed up debug compilation? How slow are we talking here? | | |
| ▲ | steveklabnik 16 hours ago | parent | next [-] | | Rust does have incremental rebuilds, yes. Folks have worked tirelessly to improve the speed of the Rust compiler, and it's gotten significantly faster over time. However, there are also language-level reasons why it can take longer to compile than other languages, though the initial guess of "because of the safety checks" is not one of them, those are quite fast. > How slow are we talking here? It really depends on a large number of factors. I think saying "roughly like C++" isn't totally unfair, though again, it really depends. | | |
| ▲ | sfink 12 hours ago | parent [-] | | My initial guess would be "because of the zero-cost abstractions", since I read "zero-cost" as "zero runtime cost" which implies shifting cost from runtime to compile time—as would happen with eg generics or any sort of global properties. (Uh oh, there's an em-dash, I must be an AI. I don't think I am, but that's what an AI would think.) | | |
| ▲ | steveklabnik 12 hours ago | parent [-] | | I used em dashes before AI, and won't stop now :) That's sort of part of it, but it's also specific language design choices that if they were decided differently, might make things faster. |
|
| |
| ▲ | esrauch 15 hours ago | parent | prev [-] | | People do have cold Rust compiles that can push up into measured in hours. Large crates often take design choices that are more compile time friendly shape. Note that C++ also has almost as large problem with compile times with large build fanouts including on templates, and it's not always realistic for incremental builds to solve either especially time burnt on linking, e.g. I believe Chromium development often uses a mode with .dlls dynamic linking instead of what they release which is all static linked exactly to speed up incremental development. The "fast" case is C not C++. | | |
| ▲ | embedding-shape 15 hours ago | parent | next [-] | | > I believe Chromium development often uses a mode with .dlls dynamic linking instead of what they release which is all static linked exactly to speed up incremental development. The "fast" case is C not C++. Bevy, a Rust ECS framework for building games (among other things), has a similar solution by offering a build/rust "feature" that enables dynamic linking (called "dynamic_linking"). https://bevy.org/learn/quick-start/getting-started/setup/#dy... | |
| ▲ | kibwen 5 hours ago | parent | prev [-] | | There's no Rust codebase that takes hours to compile cold unless 1) you're compiling a massive codebase in release mode with LTO enabled, in which case, you've asked for it, 2) you've ported Doom to the type system, or 3) you're compiling on a netbook. | | |
| ▲ | dwattttt 5 hours ago | parent [-] | | I'm curious if this is tracked or observed somewhere; crater runs are a huge source of information, metrics about the compilation time of crates would be quite interesting. | | |
|
|
| |
| ▲ | therealdkz 16 hours ago | parent | prev [-] | | [dead] |
|
| |
| ▲ | cogman10 15 hours ago | parent | prev [-] | | AFAIK, it's not the traits that does it but rather the generics. Rust does make it a lot easier to use generics which is likely why using more traits appears to be the cause of longer build times. I think it's just more that the more traits you have, the more likely you are to stumble over some generic code which ultimately generates more code. | | |
| ▲ | embedding-shape 15 hours ago | parent [-] | | > AFAIK, it's not the traits that does it but rather the generics. Aah, yes, that sounds more correct, the end result is the same, I failed to remember the correct mechanism that led to it. Thank you for the correction! |
|
|
|
| ▲ | pmarin 13 hours ago | parent | prev [-] |
| The C way is to avoid abstractions in first place. |