Skip to content
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

Can't pass byref return along until 15.7.20180719.1 #5366

Closed
lostmsu opened this issue Jul 22, 2018 · 12 comments
Closed

Can't pass byref return along until 15.7.20180719.1 #5366

lostmsu opened this issue Jul 22, 2018 · 12 comments

Comments

@lostmsu
Copy link
Contributor

lostmsu commented Jul 22, 2018

I get error FS3209: The address of the variable 'copyOfStruct' cannot be used at this point. A method or function may not return the address of this local value. when trying to wrap a function, that returns a byref on the latest preview build. In fact, the error is the same as in the release.

Repro steps

Here's the code sample, that produces the error:

type IPool<'P, 'T when 'T: struct> =
  abstract member GetReference: ITypedPointer<'P, 'T> -> byref<'T>

let Ref<'TPool, 'P, 'T when 'TPool :> IPool<'P, 'T>> (pool: 'TPool) pointer =
  pool.GetReference pointer

Also, can't define Ref as

let Ref<'TPool, 'P, 'T when 'TPool :> IPool<'P, 'T>> (pool: 'TPool) = pool.GetReference

due to a different byref error

Expected behavior

can pass byref return up

Actual behavior

can't pass byref return due to the error above

Known workarounds

Reducing generalization helps, as discovered below

let Ref<'P, 'T when 'T: struct> (pool: IPool<'P, 'T>) pointer =

Related information

Visual F# preview build 15.7.20180719.1 from https://dotnet.myget.org/F/fsharp/vsix
But also reproduces on the latest retail build (the one shipping with VS 15.7.5).

Scenario

See StackOverflow post https://stackoverflow.com/questions/51033055/byref-return-in-f-4-5

@lostmsu lostmsu changed the title Can't pass alogn byref return up until 15.7.20180719.1 Can't pass along byref return until 15.7.20180719.1 Jul 22, 2018
@lostmsu lostmsu changed the title Can't pass along byref return until 15.7.20180719.1 Can't pass byref return along until 15.7.20180719.1 Jul 22, 2018
@cartermp
Copy link
Contributor

@lostmsu Sorry, I think I'm a bit confused by the report. Are you looking to consume a byref return or produce one? Producing a byref return is not supported in F# 4.1 (as specified in the RFC).

If you are attempting to use F# 4.5 which expands support in this space, the compiler you get from the nightly feed may not contain all of what you need. I suggest waiting until VS 15.8 Preview 5, where a fully-assembled F# 4.5 will be shipped.

However, based on the error message, it seems like you're attempting to return a value from its own frame. Byrefs cannot escape the scope in which they are declared (this is also true in C#).

@zpodlovics
Copy link

zpodlovics commented Jul 24, 2018

@lostmsu You can return byrefs as [<Out>] parameters from a method (please note: this will not work with F# curried method arguments).

type IPool<'T when 'T: struct> =
  abstract member GetReference : byref<'T> -> unit
let mutable r = Unchecked.defaultof<'T>
pool.GetReference(&r)

Alternatively if you want to pass pointers around why not pass native pointers nativeptr<MyStruct>? However you will be limited with unmanaged types, and the pointer should always points to a valid location - and the GC could move your things around. You can even have byrefs that could points to native memory using an unsafe cast from https://github.com/dotnet/coreclr/issues/916#issuecomment-98583550 and #409 (comment)

@lostmsu
Copy link
Contributor Author

lostmsu commented Jul 24, 2018

@cartermp The code, that I am trying to compile is right there in the report. I want to make a F#-style wrapper for an ordinary array/span-like interface (the Ref there).

@zpodlovics Defining and implementing the interface is not the problem, the problem appears in the definition of Ref function.

@cartermp
Copy link
Contributor

@lostmsu This could get tricky. Any ByRefLike type (such as Span) is extremely restrictive in its use. You cannot store them in anything other than an additional ByRefLike struct, for example. That is, something like this:

type C(s: ReadOnlySpan<string>) =
    member this.SpanFoo : ReadOnlySpan<string>= s

Is disallowed because it would violate rules in the runtime. Your wrapper type must also be a struct that is decorated with [<IsByRefLike>]. You can read rules for these in the RFC for span.

@lostmsu
Copy link
Contributor Author

lostmsu commented Jul 24, 2018

@cartermp but I don't have an explicitly declared wrapper type 🤔

@zpodlovics
Copy link

Small repro:

This definition will work:

type IPool<'T> =
  abstract member GetReference: int -> byref<'T>

let Ref (pool: IPool<_>) = pool.GetReference 0

This definition will not work:

type IPool<'T> =
  abstract member GetReference: int -> byref<'T>

let Ref<'T,'TPool when 'TPool :> IPool<'T>>(pool: 'TPool) = pool.GetReference 0
 error FS3209: The address of the variable 'copyOfStruct' cannot be used at this point. A method or function may not return the address of this local value.

It produces the same error when 'T constrained to reference types:

type IPool<'T when 'T: not struct> =
  abstract member GetReference: int -> byref<'T>

@lostmsu
Copy link
Contributor Author

lostmsu commented Jul 24, 2018

@zpodlovics , thanks, that solves my immediate problem.

@zpodlovics
Copy link

zpodlovics commented Jul 24, 2018

These definition will also works:

type IPool<'T when 'T: unmanaged> = 
  abstract member GetPtr : int -> nativeptr<'T>

let Ref (pool: #IPool<_>) = pool.GetPtr 0

let Ref< 'T,'TPool when 'TPool :> IPool< 'T>>(pool: 'TPool) = pool.GetPtr 0

These definition will also fails:

type IPool<'T> =
  abstract member GetReference: int -> byref<'T>

let Ref (pool: #IPool<_>) = pool.GetReference 0

Somehow the generic specialization will fail when combined with byref return types.

@TIHan
Copy link
Contributor

TIHan commented Jul 24, 2018

@lostmsu , based on your example, it should work in preview 5; I just tested this myself.

type IPool<'P, 'T when 'T: struct> =
  abstract member GetReference: unit -> byref<int>

let GetRef<'TPool, 'P, 'T when 'TPool :> IPool<'P, 'T>> (pool: 'TPool) =
  &pool.GetReference()

let Ref<'TPool, 'P, 'T when 'TPool :> IPool<'P, 'T>> (pool: 'TPool) = pool.GetReference

However, your example here:

let Ref<'TPool, 'P, 'T when 'TPool :> IPool<'P, 'T>> (pool: 'TPool) = pool.GetReference

may have found some interesting things in regards to partial application on member methods for byrefs. They create different signatures than if they were let-bound functions. For the example above, the signature is: #IPool<..> -> (unit -> 'T) - if this was a let-bound function, it would be #IPool<..> -> (unit -> byref<'T>) That doesn't feel consistent, but might be intentional. They are also not regressions, as it behaves the same in 15.6.

Now, we don't support calling a partially applied function that takes a byref, but it might be something we could allow in the future.

@cartermp
Copy link
Contributor

Closing as fixed.

@majocha
Copy link
Contributor

majocha commented Dec 19, 2018

@TIHan

Now, we don't support calling a partially applied function that takes a byref, but it might be something we could allow in the future.

This seems to be a regression. It was supported at some point, because this code exists, that no longer compiles in VS

https://github.com/ReedCopsey/Gjallarhorn/blob/c9fd4f19488a04ebfb57ba398055f30df89657af/src/Gjallarhorn/CoreTypes.fs#L85-L94

image
It errors now.

@majocha
Copy link
Contributor

majocha commented Dec 19, 2018

Ah OK, it's because of the forward pipe #5286 (comment)
The rules became stricter but the error message above was not very helpful.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants