lobsterism
lobsterism

Reputation: 3509

"Mutable variables cannot be captured by closures" is giving me a bad day

I'm working with an extern DLL that has a bunch of routines that return a ReturnCode enum, so I wrote the following helper function to log all the errors:

let mutable LastError = ReturnCode.OK
let mutable LastReturnCode = ReturnCode.OK
let mutable TotalErrors = 0

let Run (call: unit -> ReturnCode) =
  LastReturnCode <- call()
  if LastReturnCode <> ReturnCode.OK then
    LastError <- LastReturnCode
    TotalErrors <- TotalErrors + 1

Great, except some of the DLL's functions have out parameters. So now when I do something like

let CreateEvfImageRef (streamHandle: int) =
  let mutable evfImageHandle = 0
  Run (fun () -> Extern.EdsCreateEvfImageRef (streamHandle, &evfImageHandle))
  evfImageHandle

the compiler gives me a "mutable variables cannot be captured by closures" error. Is there anything I can do beyond inlining Run everywhere? This worked fine in C#.

(Example extern declaration below)

[<DllImport(EDSDKPath)>]
extern ReturnCode EdsCreateEvfImageRef(int inStreamHandle, [<Out>] int& outEvfImageHandle);

Upvotes: 3

Views: 1122

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243096

You should still be able to use the ref type, but you do not need to write the & symbol when passing the reference to the function - the compiler will do this automatically:

let CreateEvfImageRef (streamHandle: int) =
  let mutable evfImageHandle = ref 0
  Run (fun () -> Extern.EdsCreateEvfImageRef (streamHandle, evfImageHandle))
  !evfImageHandle

Upvotes: 6

John Palmer
John Palmer

Reputation: 25526

The standard solution is to use references - the code becomes

let CreateEvfImageRef (streamHandle: int) =
  let evfImageHandle = ref 0
  Run (fun () -> Extern.EdsCreateEvfImageRef (streamHandle, &(!evfImageHandle)))
  !evfImageHandle

However this won't work as the compiler requires !evfImageHandle to be mutable, which it isn't.

I think the real solution here is to change your Run function so that it does not take a closure, rather have it take just the return value - which will at least have it work in this case. Then the code becomes

let Run (call: ReturnCode) =
  LastReturnCode <- call
  if LastReturnCode <> ReturnCode.OK then
    LastError <- LastReturnCode
    TotalErrors <- TotalErrors + 1

and the code changes to

let CreateEvfImageRef (streamHandle: int) =
  let mutable evfImageHandle = 0
  Extern.EdsCreateEvfImageRef (streamHandle, &evfImageHandle)) |> Run
  evfImageHandle

Or the even more hackish solution. Use the fact that array members are mutable and can be captured by closures to do

let CreateEvfImageRef (streamHandle: int) =
  let evfImageHandle =  [|0|]
  Run (fun () -> EdsCreateEvfImageRef (streamHandle, &(evfImageHandle.[0])) )
  evfImageHandle.[0]

Upvotes: 3

Related Questions