Skip to content

Instantly share code, notes, and snippets.

@zpodlovics
Last active September 27, 2015 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zpodlovics/80e12e2de35cf73e6e03 to your computer and use it in GitHub Desktop.
Save zpodlovics/80e12e2de35cf73e6e03 to your computer and use it in GitHub Desktop.
Proof of concept FSharp Struct DU
/// Author: Zoltan Podlovics
/// License: Apache License, Version 2.0, https://www.apache.org/licenses/
/// Description: Proof of concept struct discriminated union mapping to struct union (currently limited to unmanaged
/// (blittable) types)
///
/// Note: currently only works with unmanaged (blittable) types and solution or workaround may or may not exists for
/// managed types. Using it with managed types will throw System.TypeLoadException "because it contains an object field
/// at offset 8 that is incorrectly aligned or overlapped by a non-object field" when used it with managed types in
/// fields.
///
/// Usage:
/// fsharpc fsharpstructdiscriminatedunion.fs
open System.Runtime.InteropServices
open System.Runtime.CompilerServices
open Microsoft.FSharp.NativeInterop
#nowarn "9"
#nowarn "42"
type Foo =
| Bar of int
| Bar64 of int64
| Baz of int * int
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type StructBar =
val Value: int
new(v) = { Value = v }
override this.ToString() =
System.String.Format("StructBar[Value={0}]", this.Value)
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type StructBar64 =
val Value: int64
new(v) = { Value = v }
override this.ToString() =
System.String.Format("StructBar64[Value={0}]", this.Value)
//Note: this generic struct is NOT unmanaged (blittable)!
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type StructTuple2<'T1,'T2> =
val Item1: 'T1
val Item2: 'T2
new(i1,i2) = { Item1=i1; Item2=i2 }
override this.ToString() =
System.String.Format("StructTuple2[Item1={0},Item2={1}]", this.Item1, this.Item2)
//Note: this struct is unmanaged (blittable)!
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type StructTupleIntInt =
val Item1: int
val Item2: int
new(i1,i2) = { Item1=i1; Item2=i2 }
override this.ToString() =
System.String.Format("StructTupleIntInt[Item1={0},Item2={1}]", this.Item1, this.Item2)
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type StructBaz =
val Value: StructTupleIntInt
new(v) = { Value=v }
new(v1,v2) = { Value=StructTupleIntInt(v1,v2) }
override this.ToString() =
System.String.Format("StructBaz[Value={0}]", this.Value.ToString())
//Note: calculate max size of all possible case but structlayout size field require literal
[<Literal>]
let DUMaxSize = 8
// Note: required for GetValue<'T> as a maximum size struct
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1, Size=DUMaxSize)>]
type StructFooMax =
val Value: byte
//Note: workaround for field initialization order issue -> each du case will be copied to the dummy StructFooMax
let structCopy<'T,'U when 'T : unmanaged and 'U: unmanaged> (src: 'T) =
let srcPtr = NativePtr.stackalloc 1 : nativeptr<'T> in
let dstPtr = srcPtr |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<'U>
NativePtr.write srcPtr src;
NativePtr.read dstPtr
[<Struct; StructLayout(LayoutKind.Explicit)>]
type ImmutableStructFoo =
[<FieldOffset(0)>]
val TypeHandle: System.RuntimeTypeHandle
[<FieldOffset(8)>]
val ValueAsStructBar: StructBar
[<FieldOffset(8)>]
val ValueAsStructBar64: StructBar64
[<FieldOffset(8)>]
val ValueAsStructBaz: StructBaz
[<FieldOffset(8)>]
val Value: StructFooMax
static member StructBar = typeof<StructBar>
static member StructBar64 = typeof<StructBar64>
static member StructBaz = typeof<StructBaz>
//Note: check FSharp spec for field initialization order
//Note: the FSharp generated IL will always seems to initialize in the defined order in this case: ValueAsStructBar, ValueAsStructBar64, ValueAsStructBaz, Value
new(v: StructBar) = { TypeHandle=ImmutableStructFoo.StructBar.TypeHandle; ValueAsStructBar64=StructBar64(); ValueAsStructBaz=StructBaz(); ValueAsStructBar=v; Value=structCopy v }
new(v: StructBar64) = { TypeHandle=ImmutableStructFoo.StructBar64.TypeHandle; ValueAsStructBar=StructBar(); ValueAsStructBaz=StructBaz(); ValueAsStructBar64=v; Value=structCopy v }
new(v: StructBaz) = { TypeHandle=ImmutableStructFoo.StructBaz.TypeHandle; ValueAsStructBar=StructBar(); ValueAsStructBar64=StructBar64(); ValueAsStructBaz=v; Value=structCopy v }
//Note: Use Marshal.AllocHGlobal for verifiable bytecode
//Note: use a modified version of NativePtr.read IL which could cast to a specified type passed as argument
member this.GetValue<'T>() =
let ptr = NativePtr.stackalloc<StructFooMax> 1 |> NativePtr.toNativeInt
Marshal.StructureToPtr(this.Value, ptr, false);
Marshal.PtrToStructure(ptr, System.Type.GetTypeFromHandle(this.TypeHandle))
member this.GetValueNative<'T when 'T: unmanaged>() =
structCopy<StructFooMax,'T> this.Value
override this.ToString() =
System.String.Format("ImmutableStructFoo[Type={0}]", System.Type.GetTypeFromHandle(this.TypeHandle).ToString())
[<EntryPoint>]
let main argv =
printfn "%A" argv
let ibar = ImmutableStructFoo(StructBar(1))
let ibar64 = ImmutableStructFoo(StructBar64(2L))
let ibaz = ImmutableStructFoo(StructBaz(4,8))
let ibarValue = ibar.GetValue<_>()
let ibar64Value = ibar64.GetValue<_>()
let ibazValue = ibaz.GetValue<_>()
System.Console.WriteLine("Immutable Bar: {0}", ibar.ToString());
System.Console.WriteLine("Immutable Bar64: {0}", ibar64.ToString());
System.Console.WriteLine("Immutable Baz: {0}", ibaz.ToString());
System.Console.WriteLine("Immutable BarValue: {0}", ibarValue.ToString());
System.Console.WriteLine("Immutable Bar64Value: {0}", ibar64Value.ToString());
System.Console.WriteLine("Immutable BazValue: {0}", ibazValue.ToString());
0 // return an integer exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment