Matt Ashby
Matt Ashby

Reputation: 374

Option for immediate output of rlang warnings

If I create a warning using the rlang package inside a function, the warning is output after the function output:

delayed_warning <- function() {
  rlang::warn("A warning")
  Sys.sleep(10)
  "A return value"
}

delayed_warning()
#> Warning: A warning
#> [1] "A return value"

Created on 2023-09-18 with reprex v2.0.2

In some circumstances it is useful to output warnings immediately, for example when the warning is to tell the user that a function might take a long time. If I were to use the base-R warning(), I could use immediate. = TRUE to output the warning immediately.

My question: is there a way to output immediately a warning created using rlang::warn()?

(This feels like the sort of thing that I should have been able to answer with a Google search, so I suspect I'm not searching for the right terms.)

Upvotes: 2

Views: 174

Answers (2)

TimTeaFan
TimTeaFan

Reputation: 18581

I didn't realize that rlang::warn calls base R's warning() under the hood, as Ritchie Sacramento pointed out.

We can use this fact to alter the call of rlang:warn on the fly. This is some dirty function hacking, but it works.

We define a function warn2 which:

  1. takes rlang::warn
  2. extracts the function body()
  3. turns it into a list
  4. gets the last line
  5. turns the call into a list
  6. appends immediate. = TRUE argument
  7. turn this list into call
  8. overwrite the original last line
  9. overwrite the original function body
  10. call the function

This approach does work, but it has two downsides.

First, you'd need to check prior versions of rlang if rlang::warn really ends with calling warning(). In a package just set the required rlang version to the version that starts using warning() in the last line.

Second, it is of course not guaranteed that future versions of rlang will still use base R's warning() in the last line. If at one point this changes your custom warn2 function would not work anymore and we would probably need some error checks and workarounds for this.

All in all I'd say this kind of dirty function hacking is not a clean solution to your problem. But if you want to throw immediate warnings with rlang this is the way you could go. I have no idea if this kind of function hacking is allowed in a CRAN package, but I'd be curious to find out.

warn2 <- function(msg) {
  f        <- rlang::warn
  f_bdy    <- body(f)   
  f_bdy_ls <- as.list(f_bdy)
  lst_line <- length(f_bdy_ls)
  warn_cl  <- f_bdy_ls[[lst_line]]
  warn_ls  <- as.list(warn_cl)
  warn_ls  <- append(warn_ls, list(immediate. = TRUE))
  
  f_bdy_ls[[lst_line]] <- as.call(warn_ls)
  
  body(f)  <- as.call(f_bdy_ls)
  f(msg)
}

delayed_warning <- function() {
  warn2("A warning")
  Sys.sleep(10)
  "A return value"
}

delayed_warning()
#> Warning: A warning
#> [1] "A return value"

Created on 2023-09-23 with reprex v2.0.2

Upvotes: 1

Matt Ashby
Matt Ashby

Reputation: 374

After doing some further digging I found a GitHub issue requesting exactly this functionality in rlang. The issue has been closed with a note saying there is no plan to provide this.

Since outputting a warning immediately isn't possible using rlang::warn(), it seems the best workaround (as posted by @TimTeaFan in a comment) is to instead use rlang::inform(), since messages are produced immediately by default:

instant_message <- function() {
  rlang::inform("A message")
  Sys.sleep(10)
  "A return value"
}

instant_message()
#> A message
#> [1] "A return value"

Created on 2023-09-20 with reprex v2.0.2

Upvotes: 0

Related Questions