vtortola
vtortola

Reputation: 35905

F# use keyword and recursive functions

As far as I understood, the use keyword disposes the bound IDisposable as soon it is out of scope, so considering this recursive function:

let rec AsyncAcceptMessages(client : WebSocket) =
  async {
    let! message = client.AsyncReadMessage
    use reader = new StreamReader(message)
    let s = reader.ReadToEnd()
    printf "%s" <| s
    do! AsyncAcceptMessages client
  }

Let's pretend that the compiler does not find a way of using tail recursion, would that StreamReader be disposed after each recursion?

UPDATE

Tomas response show me a way of fixing it when you actually expect something back, but what if you are expecting nothing? like in this example with the StreamWriter:

let rec AsyncAcceptMessages(client : WebSocket) =
  async {
    let! message = client.AsyncReadMessage
    if(not(isNull message)) then 
      let s = 
        use reader = new StreamReader(message)
        reader.ReadToEnd()
      use writer = new StreamWriter(client.CreateMessageWriter(WebSocketMessageType.Text), Encoding.UTF8)
      writer.Write s
      printf "%s" <| s
    do! AsyncAcceptMessages client
  }

Upvotes: 3

Views: 303

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

As you're saying, the StreamReader would only be disposed of after the execution returns from the recursive call (i.e. never).

There is another issue, which is that do! is not treated as a tail-recursive call, so if you want to create an infinite tail-recursive loop, you need to use return! (otherwise your code will be leaking memory).

In this case, you can fix it easily using, because you're not doing any asynchronous operations with the StreamReader, so you can just create an ordinary local scope:

let rec AsyncAcceptMessages(client : WebSocket) =
  async {
    let! message = client.AsyncReadMessage
    let s = 
      use reader = new StreamReader(message)
      reader.ReadToEnd()
    printf "%s" <| s
    return! AsyncAcceptMessages client
  }

If you wanted to call e.g. AsyncReadToEnd, then you could do something like:

let rec AsyncAcceptMessages(client : WebSocket) =
  async {
    let! message = client.AsyncReadMessage
    let! s =
      async { use reader = new StreamReader(message)
              return! reader.ReadToEnd() }
    printf "%s" <| s
    return! AsyncAcceptMessages client
  }

Upvotes: 5

Related Questions