6 ms·
I think maybe it's because lifetime annotations can get arbitrarily complicated. If you look at enough Rust code you'll definitely see some function signatures
by ameliaquining 2y ago
I think maybe it's because lifetime annotations can get arbitrarily complicated. If you look at enough Rust code you'll definitely see some function signatures that make your head hurt, even if they're vastly outnumbered by simple ones. A guarantee that the comprehension complexity of that part of your code will always be below some low ceiling is tempting.
- whimsicalism 2y agoRust has nothing on template meta programming and the type signatures you get there, though
- nickitolas 2y agoNot to mention the error messages when you get something slightly wrong
- crest 2y agoGive the proc macro fans a little more time...
- jerf 2y agoI understand the point you are making, but C++ templates really are a uniquely disastrous programming model. They can be used to pull off neat tricks, but the way those neat tricks are done is terrible.
- duped 2y agoWhen a proc macro fails you get an error at the site where the macro is used, and a stack trace into the proc macro crate. You can even use tools to expand the proc macro to see what went wrong (although those aren't built in, yet). Debugging a proc macro failure is miles and above easier than debugging template errors.
- consteval 2y agoThis isn't really true since concepts were introduced. Granted, you have to use them, but it makes the debugging/error messages MUCH better.
- jimbob45 2y agoI’ve spent a fair amount of time writing C++ but F12’ing any of the std data structures makes me feel like I’ve never seen C++ before in my life.
- NekkoDroid 2y agoto be fair, a major cause of the pain of looking at the std is because of the naming and being semi-required to use reserved names for implementation details (either double underscore or starting underscore uppercase) and also for keeping backwards compat for older standard versions.
- thadt 2y agoIt's deceptively easy to look at a number of examples and think: "If I can see that aliasing would be a problem in this function, then a computer should be able to see that too." The article states "A C++ compiler can infer nothing about aliasing from a function declaration." Which is true, but assumes that the compiler only looks at the function declaration. In the examples given, an analyzer could look at the function bodies and propagate the aliasing requirements upward, attaching them to the function declaration in some internal data structure. Then the analyzer ensures that those functions are used correctly at every call site. Start at leaf functions and walk your way back up the program until you're done. If you run into a situation where there is an ambiguity, you throw an error and let the developer know. Do the same for lifetimes. Heck, we just got 'auto' type inference working in C++11, shouldn't we be able to do this too? I like not having to see and think about lifetimes and aliasing problems most of the time, and it would be nice if the compiler (or borrow checker) just kept track of those without requiring me to explicitly annotate them everywhere.
- seanbax 2y agoFrom P3465: "why this is a scalable compile-time solution, because it requires only function-local analysis" From P1179: "This paper ... shows how to efficiently diagnose many common cases of dangling (use-after-free) in C++ code, using only local analysis to report them as deterministic readable errors at compile time." Local analysis only. It's not looking in function definitions. Whole program analysis is extremely complicated and costly to compute. It's not comparable to return type deduction or something like that.
- account42 2y agoMaking programmers manually annoate every single function is infinitely more costly.
- dwattttt 2y agoThat rather depends. Compile time certainly wouldn't scale linearly with the size of a function, you could well reach a scenario where adding in a line to a function results in a year being added to the compile time.
- estebank 2y agoThe thing is, if you were to make the same design in C++ the code might look "cleaner" because there is less code/fewer annotations, but the other side of that coin is that the developer also has less information about how things are meant to fit together. You not only lose the compiler having your back, you also don't have useful documentation, even if that documentation would be too complicated to grasp at once. Without that documentation you might be fooled into thinking that you do understand what's going on even if you don't in reality.
- yellow_lead 2y agoThat's a good point. There's many times in a C++ codebase, where I'd see or write a seemingly innocuous function, but it has so many assumptions about lifetimes, threads, etc that it would make your brain hurt. Of course we try to remove those or add a comment, but it's still difficult to deal with.
- bluGill 2y agoThere are reasonably good c++11 conventions for lifetimes - if it is a unique_ptr you own it, otherwise you don't and shouldn't save a copy. Almost nobody follows them, but they are good conventions and you should, and write up a bug if someone else isn't. Similar, for threads, keep your data confined to one thread, but explicit where you move/copy it to a different thread (note I said move or copy - the first thread should lose access in some way) - with the only exception of data explicitly marked as thread safe. The above is forced by Rust, which would be nice, but the conventions are easy enough if you try at all. But most developers refuse to write anything more than C++98.
- duped 2y ago> But most developers refuse to write anything more than C++98. I think the bigger mistake is equating memory safety with C++11 smart pointers. They buy you a little, but not the whole buffet. There are a lot of C++ developers that think memory safety is a skill issue and if you just use "best practices with C++11 or higher" then you get it - when evidence proves to the contrary.
- myworkinisgood 2y ago[flagged]
- pjmlp 2y agoExcept those kind of annotations already exist, but have proven not to be enough withough language semantic changes, SAL is a requirement in Microsoft's own code since Windows XP SP2. https://learn.microsoft.com/en-us/cpp/code-quality/understanding-sal?view=msvc-170 https://learn.microsoft.com/en-us/cpp/code-quality/understan...
- fmbb 2y agoYes. Lifetimes are complicated. Complicated codes make them even harder. Not annotating is not making anything easier.
- HelloNurse 2y agoWhat are the "arbitrarily complicated" cases of lifetime annotations? They cannot grow beyond one lifetime (and up to one compilation error) per variable or parameter or function return value.
- ameliaquining 2y agoMostly involving structs. Someone at work once posted the following, as a slightly-modified example of real code that they'd actually written: pub struct Step<'a, 'b> { pub name: &'a str, pub stage: &'b str, pub is_last: bool, } struct Request<'a, 'b, 'c, 'd, 'e> { step: &'a Step<'d, 'e>, destination: &'c mut [u8], size: &'b Cell<Option<usize>>, } To be sure, they were seeking advice on how to simplify it, but I imagine those with a more worse-is-better technical sensibility arguing that a language simply should not allow code like that to ever be written. I also hear that higher-ranked trait bounds can get scary even within a single function signature, but I haven't had cause to actually work with them.
- steveklabnik 2y agoIn general, you can usually simplify the first one to have one lifetime for both, and in the second, you’d probably want two lifetimes, one for destination and the others all shared. Defaulting to the same lifetime for everything and then introducing more of them when needed is better than starting with a unique lifetime for each reference. I think you two are ultimately talking about slightly different things, your parent is trying to point out that, even if this signature is complex, it can’t get more complex than this: one lifetime per reference means the complexity has an upper bound.
- HelloNurse 2y agoBut you are specifying that all members of Request except step.is_last have arbitrary unrelated lifetimes (shouldn't some of them be unified?) and you are simply exposing these lifetime parameters to Request client code like you would expose C++ template parameters: a trivial repetition that is easy to read, write and reason about.