-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Champion "blittable"/"unmanaged" constraint (15.7) #187
Comments
Is this a proposal to improve existing blittable types or something? what's this about? :) |
@eyalsk It is [Redacted] ::) |
Is this related to dotnet/roslyn#2209 ? |
Sorry for the confusion. Will reopen when we have the proposal ready. |
Sorry there isn't more details here. This is being used as a reminder for me to add more details. |
Proposal added here #206 |
This is good idea! but I have some questions
|
@jaredpar Just a thought. Why can't it be an attribute?
And instead of adding a new
Allow attributes to be used as a generic constraint? so something like this.
This seems like a more general feature that can be used here and elsewhere in the framework. :) Alternatively, use a marker interface? |
@bbarry Why the downvote? what's the point of adding a new keyword? and then adding a special constraint when existing features can actually do the job or/and improved? not to mention that Maybe I'm overlooking something so if you can, please do elaborate. :) |
@eyalsk I think it's reasonable as a keyword, because adding it to a structure changes its compile-time behaviour (it means adding non-blittable fields will fail to compile). It seems like a nice fit to me. |
@eyalsk, it is nothing personal, my downvote is just that I'd rather see this be a keyword over an attribute. It is admittedly a fine line in the distinction (surely the compiled code would contain some attribute so that this can be enforced across assemblies, so the compiler must in fact know about that as it is enforced). Adding a special case to permit the use of one particular attribute for this purpose seems arbitrary and counter to syntax consistency and rules that devs can internalize when writing code (I dislike all the attributes that do this already). Allowing general constraints in the form My gut says the former is yucky and the latter is going to introduce either ambiguities or breaking changes. A keyword isn't significantly better, but I can reasonably expect that a keyword means something to a compiler and will have an impact on how my code builds. If there were the ability to have generalized attribute constraints, |
The name The name Also, how is the generic constraint enforced? I don't see a mention of a CLR change to accompany this, so would this be encoded on the generic type/method as an attribute and enforced only by supporting compilers? What would happen if an errant compiler were to use one of these generic types/methods specifying a non- |
This problem already exists in the current C# compiler. Using the using System;
using System.Runtime.CompilerServices;
struct UnmanagedStruct
{
public int X;
public int Y;
public override string ToString() => (X, Y).ToString();
}
struct ManagedStruct
{
public string X;
public string Y;
public override string ToString() => (X ?? "NULL", Y ?? "NULL").ToString();
}
unsafe class Program
{
static void Main()
{
// OK
// An unmanaged type can be used with pointers
UnmanagedStruct us = new UnmanagedStruct { X = 1, Y = 2 };
var usp = (int*)Unsafe.AsPointer(ref us);
usp[0] = 100;
usp[1] = 200;
Console.WriteLine(us); // (100, 200)
// OK!
// This is an abuse of the Unsafe class.
// The Unsafe class is implemented by IL and allows some problematic codes.
// For instance, it can make managed type pointer.
ManagedStruct ms = new ManagedStruct { X = "abc", Y = "cde" };
var msp = (IntPtr*)Unsafe.AsPointer(ref ms);
msp[0] = (IntPtr)0;
msp[1] = (IntPtr)0;
Console.WriteLine(ms); // (NULL, NULL)
msp[0] = (IntPtr)1234567; // invalid pointer. GC could be crashed
}
} |
Yes, but the existing code doesn't have any declared "constraints" intended to prevent that so the developer is definitely walking into unsafe territory there. |
It's the same.
Yes these are blittable. I missed that in the proposal. Thanks! |
In general the C# language avoids attaching semantics to attributes attached to types / methods. The preference is to use explicit language syntax to implement semantic changes in behavior. |
@jaredpar Understood, thanks. :) |
Yep ... naming is hard. 😄 One of the other names we considered in Midori was |
I like |
Agreed, and it's a minor point at least at this stage of the proposal. Even if that were the case, |
I've often seen "blit" used without a graphics connotation, more of a "spit out these raw bytes fast without parsing" connotation which is even slightly contradictory to the original meaning. |
Yeah, 'blittable' means 'can be safely treated as a single contiguous chunk of bits' to me. |
|
Is that not true for any pinned reference, a pinned byte[] array? Or are strings truly special in this regard? Wouldn't this just be a effectively a void* or a IntPtr then? Still sounds "value-ish" to me. |
To me, value-ish, no. Blit-ish, yes. For what it's worth. 😄 I think blit has more apropos meaning in people's minds (“this blob can be used without transformation”) compared to value (“this structure has value semantics rather than identity semantics”). or even |
@alzr, I'm not convinced Code that you aren't familiar with is going to be searched and |
@MarkPflug, not all pinned references are blittable. Strings are sometimes blittable, single dimensional 0-based arrays of blittable types are themselves blittable: |
Should the title of this not be renamed to lose the "blittable" naming now that the wording has, thank god, become |
Assigning back to champion since implementation is done. |
Taking a pointer to a generic with unmanaged constraint (or indirectly on a struct that is implicitly unmanaged transitively as for issue #1504) is the very primary usage of the unmanaged constraint. I can only consider the |
Oops, nevermind, retrying this with latest and it seems that pointers/fixed are supported. The only remaining problem is #1504 which can be very annoying but less critical |
Did blittable lose just because naming? I mean, blittable and unmanaged are not the same thing. E.g. char is unmanaged but not blittable. Blittable gives me the guarantee that I can take a pointer to it, read it, write it to a stream and recover it somewhere else without losing the meaning. I do not think that is true of char, I should be using a character encoding. What do we earn with unmanaged? If I have a generic type that has the constraint of being unmanaged... what are the things that I can safetly do with it thanks to that? Edit: Ah, pointers. Addendum: Can blittable exist as a subset of unmanaged? |
Assuming same CPU bitness and endian-ness?
System.Char is UTF-16, IIRC. |
Yup, from the spec:
Yes.
Character encoding is a concern of marshaling. If you aren't using marshaling (which you probably aren't when you're using the Also char is satisfies the
You need to provide examples where |
@PathogenDavid I'm not thinking of interop with unmanaged APIs, but of writing a struct to a stream (disk or network).
I must be remembering wrong. Was there not a problem of endianess? At Blittable and Non-Blittable Types char is listed not blittable. Similarly bool is listed not blittable, while it is unmanaged.
Let us say that char is blittable as you claim... Pointers are unamanaged, are they not problem? If I want to write a generic method that writes a blittable struct to disk and have it recovered by a different proccess at a later time, I should not be accepting pointers. As far as I can tell, that means that I have to check that the type is supported (well, at least that I did not get a pointer) at runtime via reflection. |
Sure, the issues are largely the same. Nothing about "marshaling" necessarily implies interop. You can use marshaling for writing data to the disk or network too.
This documentation is specific to the interop marshaler, which does not consider bool or char to be blittable because the default marshaling behavior of those types does not match their in-memory representation in CLR.
If you're worried about endianess, you can't ever handle most structs in the ways you're describing. Any primitive type larger than a byte is going to be hosed. I'd strongly suggest evaluating whether you actually care about endianess. Unless you have specific needs, it's very unlikely you actually do. I suggest you review what endianess actually is if you think it'd only apply to
It depends on what that pointer (or more specifically, the native-sized integer) represents. You can absolutely have pointers which will match between runs of the application or between multiple machines. (A few examples: This is an issue with your specific needs. Blindly writing out a struct that contains an integer field that represents an index into a separate array that wasn't written out is just as problematic.
At the end of the day, you can only do so much to protect the developer from themselves. In my opinion, passing process-specific information to some library to send over the network falls under "play stupid games, win stupid prizes". If you're working in a domain where pointer fields in structs are common and you want to avoid them getting passed to APIs that can't handle them properly, I'd suggest writing a Roslyn analyzer so you can detect this issue at compile-time. |
The blittable feature will give language enforcement to the class of types known as "unmanaged types" in the C# language spec. This is defined in section 18.2 as a type which is not a reference type and doesn't contain reference type fields at any level of nesting.
Allows
where T : unmanaged
The text was updated successfully, but these errors were encountered: