Here it is https://github.com/chromium/subspace/pull/356
The Error, Fn, FnMut and FnOnce concepts all support type erasure now, as the DynError, DynFn, DynFnMut, and DynFnOnce types respectively.
Other libs or applications can provide the same functionality for their concepts where it makes sense.
This enables the use of concept-based development without requiring the use of templates. Thus concepts can appear in virtual APIs or in functions where the body lives in a .cc file.
Otherwise, you are required to use inheritance to do the same, but you can't make everything inherit from a base class. For example an enum can match a concept but can't inherit.
And inheritance _forces_ virtual dispatch onto all users of your type. A concept-based approach allows templated code to work in the most efficient way possible, and to be completely constexpr, while now allowing non-template code access to those same concepts.
I think we can generalize the pattern of "type-erasure of a concept through a virtual interface" sufficiently that
Box<DynConcept>::from(Concept c)
can be implemented generically for any concept/template-type pair that provides this erasure.
https://godbolt.org/z/9EEznnq8a
Like Box<dyn Trait>::from(T: Trait) which is implemented for special stdlib traits in Rust.
#Cpp lets you do ~anything for better or for worse, so I guess we can make it more general in #SubspaceCpp.
I would like if you can `using X = Concept` to alias a concept from in a type, unlucky that we have to fall back to a static constexpr bool which won't participate in the better error messages that concepts allow.
It took a... few more days to work through all the ways types exist in Subspace without hitting an unreachable() in my type picker-apart-er. But you can see it live now here! See how the Result return type is made up of three links?
https://suslib.cc/sus-num-i32.html#method.try_from.signed
In a particular order, the worst things to try pull apart the types from in #Clang are:
1. Partial template specializations (template <class T> struct S<T, int>)
2. Dependent names (A::B)
3. Everything else wasn't so bad
Those 2 felt like Clang was trying to keep the information I need from me, like null pointers in the type tree where they normally give you the answer you need.
It works :blobmaracas:
Subdoc can pull apart template specializations, find all the types, and can walk back through the whole thing letting you generate links to each type individually.
There was a huge refactoring needed for this and it uh.. worked on the first try once it built. Working with strongly typed things (sum types) really works wonders for productivity and stability in any language.
Found an interesting oversight in #SubspaceCpp iterators' interaction with C++20 ranges today. I had a segfault happen in a piece of subdoc code dealing with a bunch of pointers from the Clang AST. So I rewrote it into an iterator expression that will always work if it compiles.
Problem was, it didn't work, and it was because of something I had done wrong with ranges. I had assumed that if you have an rvalue range that you now own the elements the range iterates over. This was shown false by the llvm::ArrayRef type which is a range but does not own anything.
sus::iter::from_range(an_array_ref()) would then move from the objects ArrayRef iterates over, which was incorrect. This produced temporary objects, which I then used map to get pointers from that ended up pointing into Undefined Behaviour.
C++ ranges never work with values, they always give you references, and to move from a range, you need to move from each element in the range. Moving from a reference is not a thing that exist in Rust (gladly), but it can be necessary in C++. This led to me adding the `.moved()` method on iterators, which is like `.copied()` but with a move instead. Then I had to make sus::iter::from_range() always return an iterator over references since it can't tell the right thing to do with an rvalue range.
Can I just say how nice it is to get reliable runtime errors with #SubspaceCpp Option (https://suslib.cc/sus-option-Option.html) when I write a bug in #Cpp?
Instead of the usual 5-30 minutes of debugging, I can immediately see where things went wrong and spend 5 *seconds* fixing the bug, and the remaining 2 minutes writing about it on Fedi.
https://suslib.cc is live. :blobartist:
I noticed that search engines had started indexing my #SubspaceCpp documentation where I was testing it out buried in my blog site, which would get clobbered any time I updated my blog, leaving search engines with links to 404. So I got a proper domain and the docs can stay live there.
There's still gaps to fill in for the doc generator, and docs to add to the code, but it's matured a LOT in the last week and change.
By providing the ability to construct a container like a vector in an atomic operation from an arbitrary set of inputs, we provide the ability for the library to give a safe and simple API abstraction around a powerful operation which can be implemented entirely on top of APIs that expose Undefined Behaviour. Because of the atomic nature, the use of those unsafe APIs is fully encapsulated from the application layer, allowing the use of them in a controlled manner.
Much like in how "safe Rust" applications are built on top of a stdlib full of unsafe Rust, yet retain memory safety for themselves, this creates the opportunity to build a safer security-critical C++ application without sacrificing performance.
In fact, **as we can see, we can gain performance**.
The last column below shows the relative speedup of the sus::Vec + FromIterator approach is compared to std::vector.
Full blog post here: https://danakj.github.io/2023/08/29/push-back-unchecked.html
#SubspaceCpp provides the FromIterator concept (https://github.com/chromium/subspace/blob/36d965c504a570731a0e6f377fc994a0ad1daf05/sus/iter/from_iterator.h#L28-L43and then implements this concept for sus::Vec and std::vector as well as all the types in the standard containers library (https://en.cppreference.com/w/cpp/container).
Like most of the Subspace library, this is a reimplementation of the Rust FromIterator trait (https://doc.rust-lang.org/std/iter/trait.FromIterator.html). If not already familiar with the trait and its many uses, the key points for us here are:
* You can construct a container from another container, or an iterator.
* The construction happens in a single atomic step, from the perspective of application logic, which is typically the iterator collect method.
A very nice blog post went by yesterday titled “The Little Things: The Missing Performance in std::vector”. https://codingnest.com/the-little-things-the-missing-performance-in-std-vector/
The core premise of the post is that std::vector::push_back is wasteful when you’re appending a bunch of things to a vector, since you can reserve space for them all but it keeps checking if it needs to allocate more space on every loop iteration. The author calls for a std::vector::push_back_unchecked method that appends under the assumption that space is already allocated. This exposes Undefined Behaviour if you do it wrong, of course, and the author argues that this is both necessary and good. And I agree.
My efforts lately in the #Cpp language space, including #SubspaceCpp, have been around reducing the negative impact of Undefined Behaviour and eliminating memory-safety bugs. So the above may seem counterintuitive at first glance.
Happy to see many of the issues described with C++ can be addressed in a way that is fully compatible with existing legacy codebases.
Box for non-null heap ownership, tuple(…) for returning tuple values in a concise way, clone(x) (the function) for providing a copy method that doesn’t require (yet can optionally provide) a copy assignment with it, and Clone (the concept) to allow receiving things by value in order to have the caller move without accidentally causing expensive copies.
All part of #SubspaceCpp already.
New README just dropped. What Subdoc does, and how to control it.
https://github.com/chromium/subspace/blob/main/subdoc/README.md
I miss the IRC days where there'd obviously be a #SubspaceCpp channel on freenode or something, almost before there was a github. So I went and made a discord channel where I can watch a bot report commits as they happen. Since these things are not discoverable like the IRC of old, here's a link if you want to watch the bot too. https://discord.gg/wa7ydqqp
I did another thing. Introduced `Error` in #SubspaceCpp, and found a suitable pattern to type-erase a concept implementation so that where you'd write `&dyn Error` in #RustLang you can write `const ErrorBase&` in #Cpp. And Box<ErrorBase> can be constructed from any `Error` type with `sus::into(e)`. That means you can write `Result<T, Box<ErrorBase>>` in order to put type-erased Error-concept-satisfying types into virtual functions or dynamic library ABIs.
Gotta write a boatload of tests before I put it up as a PR, but I thought this was cool. Particularly please with the repeatable mechanism for type-erasure of a concept without being intrusive at all of the type satisfying the concept. That's demonstrated here by the fact one of the error types is actually an enum.
I spent some time on #Subdoc for #SubspaceCpp this week and hit some big new milestones. Finally showing template parameters and requires clauses on functions and classes. Constructors are rendered.
And concepts are now rendered!
For example, Ord: https://danakj.github.io/subspace-docs/sus-ops-Ord.html
Also headers are self-links, links across pages have been added throughout the docs, I added some extensive docs for collections and options based on the Rust docs but C++ified.
There's now breadcrumb links at the top back to the parent namespaces.
There's lots to do but it's starting to actually look like useful docs now.
One BIG todo is to break apart a QualType into a structure that can be reconstructed into a string for display, but for which all types within it have a chance to be turned into links. Like Option<u32> could become a link to Option and to u32. Similarly, breaking a apart constraints (requires clauses) so that types within it can become links.
These need some serious #Clang knowledge though, maybe someone out there is interested in this?
“The main change between [P0907r0] and the subsequent revision is to maintain undefined behavior when signed integer overflow occurs, instead of defining wrapping behavior. This direction was motivated by: … Data from Google suggesting that over 90% of all overflow is a bug, and defining wrapping behavior would not have solved the bug.”
Safe and usable numerics that catch bugs immediately are coming soon in #SubspaceCpp
Not sure if this is good or horrible #SubspaceCpp
It works with Option, Result and any type that satisfies sus::ops::Try, if the success type is default constructible.
Only way to find out is to try use it and see how it is I guess.
I wish #Cpp had pattern matching.
Appreciate seeing Dr Stroustrup advocating for being able to borrow check #Cpp. He didn't call it that but:
- static analysis that prevents dangling pointers
- static analysis that prevents invalidation (mutable aliasing violations)
That's borrow checking.
Also appreciate the callout for the ability to:
- ban pointer arithmetic outside of containers
- catch nullptr deref at runtime
- bounds checks in span/vector
- catch integer overflow, prevent narrowing conversions, prevent implicit sign conversions
https://www.youtube.com/watch?v=eo-4ZSLn3jc
Bounds checking can be done today by enabling libc++ hardening, if you're using libc++. Getting that into other std libs would be good. However there's no explicit backdoor past the checking, which makes unideal security outcomes (people replace the types entirely).
Banning pointer arithmetic is also something clang is enabling. Again, standardizing this would be excellent. This one comes with an unsafe backdoor. Clang uses pragmas to opt into pointer arithmetic but they are specific to that one thing.
[[enforce(P)]] and [[provide(P)]] sound like the kind of things that could generalize those pragmas and let me get rid of the `unsafe_fn` parameter in the "UB lies here" functions in Subspace. I hope so anyway.
Safe integer behaviour is available now in #SubspaceCpp with explicit UB backdoors in the same ways as Rust integers.
I have implemented Iterator::try_collect in #Cpp ! :blobartist:
https://github.com/chromium/subspace/pull/308
This is all because of https://social.treehouse.systems/@megmac/110795822833932733
The templates involved are kind of wild. I needed to add a means to convert from one Try type in an error to another state to another Try type with a compatible error state, and a concept to guard for that. This is like https://doc.rust-lang.org/stable/std/ops/trait.FromResidual.html but since `void` is not really a type we can't do it in the same way as Rust does it.
The whole Try concept has been a bit of a pain because of void. But I also found a good way to support try_for_each() with the same API in the presence of Result<void, E> earlier today.
This was really gnarly to get it to compile with all the indirection through Try types so I kinda screamed when it compiled and passed a test on the first try. :BlobCat_Giggle:
Maybe Rust can get this lovely method in Stable one day. Who will get there first?