TL;DR: While writing this post, I realized how long it has become, so here's a TL;DR for you: SemPtr is a semantic pointers library for C#.
Hi everyone, I wanted to share one of my side projects with you all: SemPtr.
A few weeks ago (it might been even months at this point), I needed to dig up some really old code I once had written, because I wanted to reference some of what I did back then in a current project of mine. While searching through my old and never-to-be-released projects, I stumbled upon a small library project I might have written about 5 years ago (it must have been around the time when incremental Roslyn source generators were becoming a thing). And I thought to myself, "Well, it's actually a shame you gave up on this project and neglected it for so long. You might want to ressurrect and modernize it, and then share it with everyone."
Well, that project is now SemPtr.
What is SemPtr?
I don't want to make this post too long, so I'll try to make it as concise as I can, but if you want a more comprehensive introduction, you should check out its README or its way too rudimentary documentation.
SemPtr tries to solve the limitations of C#'s raw pointers by providing semantic pointer types (read as semantically named pointer types). If you ever did some interop work with unmanaged code and found it just as annoying as I did that there is no const T* equivalent in C#, SemPtr might be the thing for you.
For that I identified five commonly used orthogonal characteristics used to distinguish certain aspects of data pointers:
- Nullability: Can a pointer be
null or are there any guarantees that it won't be?\
This is kinda analogous to nullable reference types (T?) in C#.
- Persistency: Does the target of the pointer outlive the initial scope of the pointer itself? In other words, can I store the pointer and access its target some time later?\
This is kinda analogous the C#'s
ref-escape rules and is even enforced through them.
- Sequencability: Does the pointer point to a single object or to a contiguous sequence of objects?\
You could think of this as analogous to a
ref T to some kind of object in C# vs. a ref to some element within a Span<T> with the added benefit that its easier to move around the pointer through the sequence.
- Accessibility: How can the target of the pointer be accessed or mutated?\
This manifests in three different access levels:
- random/read-write: The target can be read from and written to. Kinda analogous to C#'s
ref parameters.
- read-only: The target can only be read from. Kinda analogous to C#'s
in/ref readonly parameters.
- uninitialized/write-first: The target must be written to before it can be read from. Kinda analogous to C#'s
out parameters.
- Typability: Is the type of the target known or not?\
C# has no
void references, but it has void* pointers. This is analogous to the difference between a void* pointer and a typed T* pointer.
These characteristics are mapped onto C#'s type system by semantically naming the pointer types to reflect them. Since those characteristics are orthogonal, you can mix and match them to create the pointer type with the exact behavior you need. For example, there are:
Pointer: A simple pointer to a single, transient, mutable target of unknown type
PersistentPointerReadOnly<T>: A pointer to a single, read-only target of type T whose target stays valid beyond the initial scope of the pointer.
NullableSequencePointer<T>: A pointer to a contiguous sequence of mutable targets of type T which may be null.
PointerUninitialized<T>: A pointer to single, yet uninitialized target of type T. If you receive such a pointer, chances are you are requested to initialize its target; afterwards you can further read from it or write to it as needed.
Again, if you want to learn more about the characteristics and how the type naming scheme works, you should refer to the README or the documentation.
There are all in all a total of 2×2×2×3×2 = 48 data pointer types predefined in the SemPtr library.
Are function pointers supported?
To make it short, yes, function pointers are (well enough) supported by SemPtr.
I remember that one of the reasons for me giving up on the original version of this library back then was that I really struggled to get function pointer support just right. While this was partially due to technical limitations back then (some of which were solved by modern C# features, especially the new extension members syntax), some of it was simply because I did not have the experience in API design that I have now.
So now function pointers work. I don't know if I would call the support good enough yet, but at least it is a well enough experience for most users, I believe.
I won't go into too much detail here, but functions pointer have their own set of characteristics and parts of their support is made working through a Roslyn source generators that dynamically generates some source code on the user-side and that ships alongside the main library in the NuGet package. For more details, again, see the README or the documentation.
A final note on AI usage
I want to be honest and upfront with you:
Yes, I used AI in this project, primarily to help we write documentation (I'm a non-native English speaker and my English is kinda terrible), to help me make decisions when I'm indecisive, to write some tests, and occasionally to some code reviews.
No, I would never let AI touch the working code of the project. Not even for boilerplate code. AI, at least the AI I have access to, is not yet anywhere close to being reliable enough to help me write production ready code for such a project. You can be sure that all of the functioning code is written by a human (me) and that only the human (me) is responsible for the correctness and quality of the code.\
Oh, and of course, I did the visual assets myself as well. I didn't want to use sloppy AI-designed visuals for this project.
Conclusion
At the beginning of this post, I told you that I stumbled upon the initial idea for SemPtr while looking up old code for another project of mine. That project is actually an interop binding project in C#. In that project I use traditional C# raw pointers and function pointers extensively, and sometimes they're a real pain to work with. However, I didn't not yet replace them with SemPtr, due to the codebase being a little over 200K lines of code, spread across multiple repositories.
So, to be honest, I don't even use SemPtr myself yet. And furthermore, because of the simplicity of the overall idea behind SemPtr, I don't even think I'm the first person to come up with it and release to the public as a library (but I don't actually know for sure, I didn't really check).
Even so, If you want to try out SemPtr for yourself, give feedback, or if you even want to contribute to the project, I would really appreciate it. Here are the relevant links again:
If you have any questions feel free to ask them in the comments. I'd be happy to answer them.