-
Notifications
You must be signed in to change notification settings - Fork 145
RFC-1090 for unmanaged generic struct types #480
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
Merged
cartermp
merged 17 commits into
fsharp:master
from
vzarytovskii:rfc-1090-unmanaged-generic-structs
Jul 27, 2020
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
f403951
Added an RFC for https://github.com/fsharp/fslang-suggestions/issues/692
vzarytovskii 5f764f3
Simplify summary
vzarytovskii 2cbed50
Added more verbose error message example; Change wording in the detai…
vzarytovskii e617965
Added more examples; Added description of the unmanaged types; Added …
vzarytovskii 2d40d57
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 942c52c
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 4860a97
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii a4de907
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 1802bf0
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii e7893f8
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 9bc2e68
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii b044e72
Address RFC comments; Added modreq requirement; Added more examples; …
vzarytovskii 00b9f0f
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 6d139c2
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii db2c05d
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii a6f9b70
Update RFCs/FS-1090-Generic-struct-type-whose-fields-are-all-unmanage…
vzarytovskii 6d3bf33
Address PR comments.
vzarytovskii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
288 changes: 288 additions & 0 deletions
288
...S-1090-Generic-struct-type-whose-fields-are-all-unmanaged-types-is-unmanaged.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,288 @@ | ||
| # F# RFC FS-1088 - (Generic struct type whose fields are all unmanaged types is unmanaged) | ||
|
|
||
| The design suggestion [Generic struct type whose fields are all unmanaged types is unmanaged](https://github.com/fsharp/fslang-suggestions/issues/692) has been marked "approved in principle". | ||
| This RFC covers the detailed proposal for this suggestion. | ||
|
|
||
| * [x] Approved in principle | ||
| * [x] [Suggestion](https://github.com/fsharp/fslang-suggestions/issues/692) | ||
| * [ ] Implementation: [In progress](https://github.com/dotnet/fsharp/pull/6064) | ||
|
|
||
| # Summary | ||
|
|
||
| [summary]: #summary | ||
|
|
||
| Allow generic structs to be `unmanaged` at construction if all fields are `unmanaged` types. | ||
|
|
||
| # Motivation | ||
|
|
||
| [motivation]: #motivation | ||
|
|
||
| * Make it easier to author low-level code. | ||
| * Improve interoperability with C#. | ||
|
|
||
| # Detailed design | ||
|
|
||
| [design]: #detailed-design | ||
|
|
||
| Currently, it is not possible to define a generic struct with the `unmanaged` constraint and substitute the type with another generic struct, even if its type is also constrained to be `unmanaged`. | ||
|
|
||
| Consider the following code: | ||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type MyStruct(x: int, y: int) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| [<Struct>] | ||
| type MyStructGeneric<'T when 'T: unmanaged>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| [<Struct>] | ||
| type MyStructGenericWithNoConstraint<'T>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| [<Struct>] | ||
| type Test<'T when 'T: unmanaged> = | ||
| val element: 'T | ||
|
|
||
| let works = Test<int>() | ||
| let works2 = Test<MyStruct>() | ||
|
|
||
| // error FS0001: A generic construct requires that the type 'MyStructGeneric<int>' is an unmanaged type | ||
| let error = Test<MyStructGeneric<int>>() | ||
|
|
||
| // error FS0001: A generic construct requires that the type 'MyStructGenericWithNoConstraint<int>' is an unmanaged type | ||
| let error = Test<MyStructGenericWithNoConstraint<int>>() | ||
| ``` | ||
|
|
||
| Prior to this RFC, the example above will fail to compile with: | ||
|
|
||
| ```less | ||
| let error = Test<MyStructGeneric<int>>() | ||
| ------------^^^^^^^^^^^^^^^ | ||
|
|
||
| stdin(6,13): error FS0001: A generic construct requires that the type 'MyStructGeneric<int>' is an unmanaged type | ||
| ``` | ||
|
|
||
| This proposal aims to resolve this inconsistency by treating any generic struct type as `unmanaged` if all its fields are unmanaged. | ||
|
|
||
| For example, consider the following code: | ||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type MyStructGeneric<'T when 'T: unmanaged>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| [<Struct>] | ||
| type MyStructGenericWithNoConstraint<'T>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
| ``` | ||
|
|
||
| Instances of both `MyStructGeneric<'T>` and `MyStructGenericWithNoConstraint<'T>` will be treated as unmanaged type as long as `'T` is being unmanaged. | ||
|
|
||
| In other words, any generic struct type, with all of its fielads are known to be unmanaged, can be considered unmanaged, _with_ or _without_ the `unmanaged` constraint on type parameter(s) | ||
|
|
||
| ## Adjusted definition of an unmanaged type | ||
|
|
||
| __The existing definition of an unmanaged type is any type that isn't a reference-type and contains no fields whose type is not an unmanaged type:__ | ||
|
|
||
| * `sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool`. | ||
| * Any enum-type. | ||
| * Any pointer-type. | ||
| * Any non-generic user-defined struct type that contains fields of unmanaged-types only. | ||
|
|
||
| __We adjust this definition by modifying the last section:__ | ||
|
|
||
| * Any user-defined struct type that can be statically determined to be 'unmanaged' at construction. | ||
|
|
||
| Examples: | ||
|
|
||
| ```fsharp | ||
| // Always unmanaged (forced by constraint). | ||
| [<Struct>] | ||
| type MyStructGeneric<'T when 'T: unmanaged>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| // Can be considered unmanaged, as long as 'T is unmanaged. | ||
| [<Struct>] | ||
| type MyStructGenericWithNoConstraint<'T>(x: 'T, y: 'T) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| // Can be considerd unmanaged, as long as 'T is unmanaged. | ||
| // Note, that despite constructor having managed argument (obj), it is not used as part of the backing field. | ||
| [<Struct>] | ||
| type MyStructGenericWithUnusedUnmanagedParameter<'T>(x: 'T, y: 'T, z: obj) = | ||
| member _.X = x | ||
| member _.Y = y | ||
|
|
||
| // Not unmanaged, since it has a field with a managed type. | ||
| [<Struct>] | ||
| type MyStructGenericWithUnmanagedField<'T>(x: 'T, y: 'T, z: obj) = | ||
| member _.X = x | ||
| member _.Y = y | ||
| member _.Z = z | ||
| ``` | ||
|
|
||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Implementation details | ||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| * Any existing generic struct type will be considered unmanaged when all of its fields are unmanaged types. | ||
| * `IsUnmanagedAttribute` should be emitted on type arguments with the `unmanaged` constraints (for interop). | ||
| * Treat type arguments with `IsUnmanagedAttribute` as if they have the `unmanaged` constraint (for interop). | ||
| * [`UnmanagedType`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedtype?view=netcore-3.1) modreq should be emitted on type argument (for interop). | ||
| * An `unmanaged` constraint cannot be used together with `not struct` constraint (not the case right now). | ||
|
|
||
| ## Supported types | ||
|
|
||
| * Generic struct types whose fields are unmanaged | ||
| * Struct records with unmanaged fields | ||
| * Anonymous struct records with unmanaged fields | ||
| * Struct tuples with unmanaged values | ||
| * Struct unions (single and multi-case) whose fields are all unmanaged | ||
|
|
||
cartermp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Examples | ||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The following examples are now possible with this feature: | ||
|
|
||
| Basic examples: | ||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type S<'T, 'U> = | ||
|
|
||
| [<DefaultValue(false)>] val X : 'T | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let passing () = | ||
| test (S<float, int>()) | ||
|
|
||
| let passing2 () = | ||
| test (S<float, obj>()) // passes as the 'obj' type arg is not used as part of the backing field of a struct | ||
|
|
||
| let passing3<'T when 'T : unmanaged> () = | ||
| test (S<'T, obj>()) | ||
|
|
||
| let not_passing () = | ||
| test (S<obj, int>()) | ||
|
|
||
| let not_passing2<'T> () = | ||
| test (S<'T, obj>()) | ||
| ``` | ||
|
|
||
| Constructor: | ||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type S<'T> = | ||
|
|
||
| val X : 'T | ||
|
|
||
| new (x) = { X = x } | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let passing () = | ||
| test (S(1)) | ||
|
|
||
| let not_passing () = | ||
| test (S(obj())) | ||
| ``` | ||
|
|
||
| Nested generics: | ||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type W<'T> = { x: 'T } | ||
|
|
||
| [<Struct>] | ||
| type S<'T> = { x: W<'T> } | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let passing () = | ||
| test Unchecked.defaultof<S<int>> | ||
|
|
||
| let not_passing () = | ||
| test Unchecked.defaultof<S<obj>> | ||
| ``` | ||
|
|
||
| Union types: | ||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type Container<'a> = Container of 'a | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let passing () = test (Container 1) | ||
|
|
||
| let not_passing () = test (Container "string") | ||
| ``` | ||
|
|
||
| Tuples: | ||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```fsharp | ||
| let x = struct(1,2,3) | ||
|
|
||
| let y = struct("s", "t", "r", "i", "n", "g") | ||
|
|
||
| let z = struct(1, 2, 3, "s", "t", "r") | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let passing () = test (x) | ||
|
|
||
| let not_passing () = test (y) | ||
|
|
||
| let not_passing2 () = test (z) | ||
| ``` | ||
|
|
||
| Records: | ||
vzarytovskii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```fsharp | ||
| [<Struct>] | ||
| type Point = { X: float; Y: float; Z: float; } | ||
|
|
||
| [<Struct>] | ||
| type Person = { Name: string; Age: int; } | ||
|
|
||
| let test (x: 'T when 'T : unmanaged) = () | ||
|
|
||
| let mypoint = { X = 1.0; Y = 1.0; Z = -1.0; } | ||
|
|
||
| let passing () = test (mypoint) | ||
|
|
||
| let passing2 () = test (struct {| A= 1 |}) | ||
|
|
||
| let person = { Name = "Joe"; Age = 42 } | ||
|
|
||
| let not_passing () = test (person) | ||
|
|
||
| let not_passing2 () = test (struct {| S = "str" |}) | ||
| ``` | ||
|
|
||
| ## Documentation | ||
|
|
||
| 'Unmanaged Constraint' part of [F# constraints documentation](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/constraints) should be updated to reflect new supported cases. | ||
|
|
||
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| The major drawback of the feature is that it serves a relatively small number of developers, since most of the F# projects are not focused on a low-level programming. | ||
|
|
||
| # Alternatives | ||
| [alternatives]: #alternatives | ||
|
|
||
| There are currently no alternatives or workarounds for this. | ||
|
|
||
| # Unresolved questions | ||
| [unresolved]: #unresolved-questions | ||
|
|
||
| None. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.