# Usage of the lifecycle package

library(lifecycle)

Use lifecycle to document the status of your exported functions and arguments:

• Choose one of the 7 lifecycle stages a function or argument can be in. You can choose from 4 development stages (experimental, maturing, stable, and questining) and 3 deprecation stages (soft-deprecated, deprecated, and defunct).

• If the function or argument is deprecated, make sure your users know about by calling deprecate_soft(), deprecate_warn(), or deprecate_stop(). These functions try to be informative without being too verbose, with increasing levels of verbosity as the deprecation stage advances.

### Stages

The lifecycle stages for functions and arguments closely mirror the lifecycle stages for packages.

There are 4 development stages.

1. This is a new feature that is in the very early stage of development. It is exported so users can start to use it and report feedback, but its interface and/or behaviour is likely to change in the future. It is generally best to avoid depending on experimental features.

2. The interface and behaviour of a maturing feature has been roughed out, but finer details are likely to change. It still needs more feedback to find the optimal API.

3. A feature is considered stable when the author is happy with its interface and behaviour. Major changes are unlikely, and breaking changes will occur gradually, through a deprecation process.

4. The author is no longer convinced that the feature is the optimal approach. However, there are no recommended alternatives yet.

Once the decision of discontinuing a feature has been made, it goes through 3 deprecation stages.

1. The author is no longer happy with a feature because they consider it sub-optimal compared to some other approach, or simply because they no longer have the time to maintain it. A soft-deprecated feature can still be used without hassle, but users should consider switching to an alternative approach.

2. The feature is likely to be discontinued in the next major release. Users should switch to an alternative approach as soon as possible.

3. The feature can no longer be used. A defunct function is still exported, and a defunct argument is still part of the signature. This way an informative error can be thrown.

Finally, when a feature is no longer exposed or mentioned in the released version of the package, it is said to be archived.

• Call usethis::use_lifecycle() to import the badges in your package.

• Use the lifecycle Rd macro to insert a badge:

#' \lifecycle{experimental}
#' \lifecycle{soft-deprecated}

This badge renders as text in non-HTML documentation. To document the status of a whole function, a good place to include the badge is at the top of the @description block. To document an argument, you can put the badge in the argument description.

• For functions in development, you typically don’t need to advertise the status if it is the same as the package as a whole. For instance, if your package is maturing, only signal functions in the experimental, stable, and questioning stages.

### Verbosity of deprecation

lifecycle offers three levels of verbosity corresponding to the three deprecation stages.

• Soft deprecation: At this stage, call deprecate_soft() to start warning users about the deprecation in the least disruptive way.

This function only warns (a) users who try the feature from the global workspace, and (b) developers who directly use the feature, when they run unit tests with testthat. No warning is issued outside of unit tests, or when the deprecated feature is called from another package then ther own.

When a warning does get issued, users only see it once per session rather than at each invokation.

• Deprecation: At this stage, call deprecate_warn() to warn unconditionally about the deprecated feature. The warning is issued only once per session.

• Defunct: The feature is discontinued. Call deprecate_stop() to fail with an error.

### Deprecating functions

These functions take the version number starting from which the feature is considered deprecated (it should remain the same across all deprecation stages), and a feature descriptor:

deprecate_warn("1.0.0", "mypkg::foo()")
#> Warning: foo() is deprecated as of mypkg 1.0.0.
#> This warning is displayed once per session.
#> Call lifecycle::last_warnings() to see where this warning was generated.

You can optionally provide a replacement:

deprecate_warn("1.0.0", "mypkg::foo()", "new()")
#> Warning: foo() is deprecated as of mypkg 1.0.0.
#> Please use new() instead.
#> This warning is displayed once per session.
#> Call lifecycle::last_warnings() to see where this warning was generated.

For the purpose of these examples we explicitly mentioned the namespace with mypkg::, however you can typically omit it because lifecycle infers the namespace from the calling environment. Specifying the namespace is mostly useful when the replacement is implemented in a different package.

# The new replacement
foo + bar
}

# The old function still exported for compatibility
}

### Deprecating arguments

The syntax for deprecating argument is based on the syntax for deprecating functions:

deprecate_warn("1.0.0", "mypkg::foo(arg = )")
#> Warning: The arg argument of foo() is deprecated as of mypkg 1.0.0.
#> This warning is displayed once per session.
#> Call lifecycle::last_warnings() to see where this warning was generated.

deprecate_warn("1.0.0", "mypkg::foo(arg = )", "mypkg::foo(new = )")
#> Warning: The arg argument of foo() is deprecated as of mypkg 1.0.0.
#> Please use the new argument instead.
#> This warning is displayed once per session.
#> Call lifecycle::last_warnings() to see where this warning was generated.

lifecycle also provides the deprecated() sentinel to use as default argument. This provides self-documentation for your users, and makes it possible for external tools to determine which arguments are deprecated. This sentinel is simply the missing argument, so you can test whether the argument was supplied with rlang::is_missing():

foobar_adder <- function(foo, bar, baz = deprecated()) {
# Check if user has supplied baz instead of bar
if (!rlang::is_missing(baz)) {

# Signal the deprecation to the user

# Deal with the deprecated argument for compatibility
bar <- baz
}

foo + bar
}

### Workflow

#### Where do these deprecation warnings come from?

Call lifecycle::last_warnings() to see backtraces for all the deprecation warnings that were issued during the last top-level command.

#### Bumping deprecation stage

Some manual search and replace is needed to bump the status of deprecated features. We recommend starting with defunct features and work your way up:

1. Search for deprecate_stop() and remove the feature from the package. The feature is now archived.

2. Search for deprecate_warn() and replace with deprecate_stop().

3. Search for deprecate_soft() and replace with deprecate_warn().

4. Call deprecate_soft() from newly deprecated functions.

Don’t forget to update the badges in the documentation topics.

#### Find out what deprecated features you rely on

Test whether your package depends on deprecated features directly or indirectly by setting the verbosity option in the tests/testthat.R file just before test_check() is called:

library(testthat)
library(mypackage)

options(lifecycle_verbosity = "error")
test_check("mypackage")

This forces all deprecated features to fail. You can also set the relevant options manually to force warnings or errors in your session:

# Force silence
options(lifecycle_verbosity = "quiet")

# Force warnings
options(lifecycle_verbosity = "warning")

# Force errors
options(lifecycle_verbosity = "error")

Forcing warnings can be useful in conjuction with last_warnings(), which prints backtraces for all the deprecation warnings issued during the last top-level command.

#### Test deprecated features

Test whether a deprecated feature still works by disabling the warnings with scoped_lifecycle_silence():

test_that("baz argument of foobar_adder() still works", {
withr::local_options(list(lifecycle_verbosity = "quiet"))
})

Test that a feature is correctly deprecated by forcing warnings and checking the class "lifecycle_warning_deprecated":

test_that("baz argument of foobar_adder() is deprecated", {
withr::local_options(list(lifecycle_verbosity = "warning"))
expect_warning(foobar_adder(1, baz = 2), class = "lifecycle_warning_deprecated")
})

Defunct features throw errors of class "lifecycle_error_deprecated":

test_that("foo() is defunct", {
expect_error(foo(), class = "lifecycle_error_deprecated")
})