Search code examples
f#pass-by-referencecomputation-expression

How do you pass/set byref arguments within a computation expression?


This F# code is failing to compile:

let DoubleString (str : string) = str + str

let ExampleSetString (str : string byref) = 
    async {
        str <- DoubleString str
        return 1
    }

I get the following error message on the line str <- DoubleString str:

The byref-typed variable 'str' is used in an invalid way. Byrefs cannot be captured by closures or passed to inner functions

I must take a byref variable, pass it into some pure functions within a computation expression and then set the value of this byref variable. How can I do this?


Solution

  • Dotnet restricts storing byref variables into fields, because byref can point at

    1. array element (&ar[42])
    2. field of object (&myObject.MyField)
    3. local variable (&myLocalVar)
    4. unmanaged memory (&(System.Runtime.CompilerServices.Unsafe.AsRef<myStruct> myVar))
    5. probably more

    This leads to a lifetime problem: reference can outlive variable it's pointing at. It won't happen with case 1, but can happen with all other items. For example given function Foo calling Bar and passing value by reference will produce this stack

     Baz function   Foo function   Bar function
    /            \ /            \ /            \
    --------------|---a----------|----b---------
    

    Foo have called Bar and passed it a as reference &a. Bar have stored address of a into variable b. This means that b is byref-variable, updates on this variable will change value in a. When function completes it's work, memory is freed and can be reused by other function.

     Baz function   Egg function
    /            \ /            \
    --------------|---c----------
    

    Baz have called Egg function, and now at memory where was a now lies c. Attempting to change reference to a now can cause all sort of memory problems, from AccessViolation (also known as Segmentation Fault in Linux), to data corruption. Worst thing that can happen when memory is corrupted, is for program to continue it's work.

    That's the reason why byref variables cannot be captured into closures, which are produced by computation expression.


    Now let's return to actual problem - store string intro Azure blob. I'm not familiar with it, but I've found example for this, which can be adapted to following

    let DoubleString (str : string) = str + str
    
    let ExampleSetString (containerClient : BlobContainerClient) (str : string) = 
        task {
            let client = containerClient.GetBlobClient("path to where to store")
            let doubled = DoubleString str
            do! client.UploadAsync(BinaryData.FromString(double), ?overwrite=true)
        }