From ee6506c9a9b7bff76d88b9d38ac45e68abeda0ef Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Sat, 25 Oct 2025 09:58:21 -0700 Subject: [PATCH] add default args example --- .gitignore | 3 +- .../default-args/execute-results/html.json | 15 +++ _quarto.yml | 1 + user-guide/default-args.qmd | 108 ++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 _freeze/user-guide/default-args/execute-results/html.json create mode 100644 user-guide/default-args.qmd diff --git a/.gitignore b/.gitignore index 39a204d..ce6ff5a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ Cargo.lock *.pdb # r specific .Rproj.user/ -*.Rproj \ No newline at end of file +*.Rproj +**/*.quarto_ipynb diff --git a/_freeze/user-guide/default-args/execute-results/html.json b/_freeze/user-guide/default-args/execute-results/html.json new file mode 100644 index 0000000..2b5d304 --- /dev/null +++ b/_freeze/user-guide/default-args/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "0e65dcf08a918c5401ff7a49e3a425dc", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: Default function arguments\ndate: \"2025-10-25\"\nfreeze: true\n---\n\n\n\nIn Rust, all function arguments are required—there are no default arguments. However, for R packages, having default arguments is essential for creating ergonomic and user-friendly APIs. The `#[extendr]` macro provides the `default` parameter allowing you to specify default values that will be added to the generated R wrapper functions.\n\n## The `default` attribute\n\nThe `default` attribute is applied to individual function arguments using the syntax `#[extendr(default = \"value\")]`. The value you provide is a string that will be inserted directly into the generated R function signature. This means you can use any valid R expression as a default value.\n\n```rust\n#[extendr]\nfn my_function(#[extendr(default = \"NULL\")] x: Robj) {\n // function body\n}\n```\n\nThis generates an R function with the signature `my_function(x = NULL)`.\n\n## Basic examples\n\nLet's start with a simple function that checks if an argument is `NULL`. By setting the default to `\"NULL\"`, users can call the function without providing any arguments:\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn check_default(#[extendr(default = \"NULL\")] x: Robj) -> bool {\n x.is_null()\n}\n```\n:::\n\n\nNow you can call this function without any arguments, and it will use the default value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncheck_default()\n#> [1] TRUE\n```\n:::\n\n\nOr provide an explicit value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncheck_default(42)\n#> [1] FALSE\n```\n:::\n\n\n## Working with logical defaults\n\nDefault arguments work with any R type. Here's an example with a logical default value:\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn greet(name: &str, #[extendr(default = \"FALSE\")] loud: bool) -> String {\n let greeting = format!(\"Hello, {}\", name);\n if loud {\n greeting.to_uppercase()\n } else {\n greeting\n }\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Using the default (quiet greeting)\ngreet(\"Alice\")\n#> [1] \"Hello, Alice\"\n\n# Override the default\ngreet(\"Alice\", loud = TRUE)\n#> [1] \"HELLO, ALICE\"\n```\n:::\n\n\n## Multiple defaults\n\nYou can use multiple default arguments in a single function. Just remember that in R, arguments with defaults should typically come after required arguments for best practices:\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn multiply(\n x: f64,\n #[extendr(default = \"1.0\")] multiplier: f64,\n #[extendr(default = \"FALSE\")] round_result: bool\n) -> f64 {\n let result = x * multiplier;\n if round_result {\n result.round()\n } else {\n result\n }\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# All defaults\nmultiply(5.5)\n#> [1] 5.5\n\n# Custom multiplier\nmultiply(5.5, multiplier = 2.5)\n#> [1] 13.75\n\n# Custom multiplier and rounding\nmultiply(5.5, multiplier = 2.5, round_result = TRUE)\n#> [1] 14\n```\n:::\n\n\n## Important notes\n\n- The value in `default = \"...\"` is inserted directly into the R function signature, so it must be valid R code\n- You can use any R expression, including function calls: `#[extendr(default = \"getOption('my_option')\")]`\n- The default values only affect the R wrapper—your Rust function still receives whatever value the R user provides (or the default if they don't)\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index 8839f3e..7ab875d 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -75,6 +75,7 @@ website: - section: "Error Handling" contents: - user-guide/error-handling/basic-error-handling.qmd + - user-guide/default-args.qmd - user-guide/serde-integration.qmd - user-guide/tokio.qmd - user-guide/cran-publishing.qmd diff --git a/user-guide/default-args.qmd b/user-guide/default-args.qmd new file mode 100644 index 0000000..09ce4b4 --- /dev/null +++ b/user-guide/default-args.qmd @@ -0,0 +1,108 @@ +--- +title: Default function arguments +date: "`r Sys.Date()`" +freeze: true +--- + +```{r, include = FALSE} +library(rextendr) +``` + +In Rust, all function arguments are required—there are no default arguments. However, for R packages, having default arguments is essential for creating ergonomic and user-friendly APIs. The `#[extendr]` macro provides the `default` parameter allowing you to specify default values that will be added to the generated R wrapper functions. + +## The `default` attribute + +The `default` attribute is applied to individual function arguments using the syntax `#[extendr(default = "value")]`. The value you provide is a string that will be inserted directly into the generated R function signature. This means you can use any valid R expression as a default value. + +```rust +#[extendr] +fn my_function(#[extendr(default = "NULL")] x: Robj) { + // function body +} +``` + +This generates an R function with the signature `my_function(x = NULL)`. + +## Basic examples + +Let's start with a simple function that checks if an argument is `NULL`. By setting the default to `"NULL"`, users can call the function without providing any arguments: + +```{extendrsrc engine.opts = list(use_dev_extendr=TRUE)} +#[extendr] +fn check_default(#[extendr(default = "NULL")] x: Robj) -> bool { + x.is_null() +} +``` + +Now you can call this function without any arguments, and it will use the default value: + +```{r} +check_default() +``` + +Or provide an explicit value: + +```{r} +check_default(42) +``` + +## Working with logical defaults + +Default arguments work with any R type. Here's an example with a logical default value: + +```{extendrsrc engine.opts = list(use_dev_extendr=TRUE)} +#[extendr] +fn greet(name: &str, #[extendr(default = "FALSE")] loud: bool) -> String { + let greeting = format!("Hello, {}", name); + if loud { + greeting.to_uppercase() + } else { + greeting + } +} +``` + +```{r} +# Using the default (quiet greeting) +greet("Alice") + +# Override the default +greet("Alice", loud = TRUE) +``` + +## Multiple defaults + +You can use multiple default arguments in a single function. Just remember that in R, arguments with defaults should typically come after required arguments for best practices: + +```{extendrsrc engine.opts = list(use_dev_extendr=TRUE)} +#[extendr] +fn multiply( + x: f64, + #[extendr(default = "1.0")] multiplier: f64, + #[extendr(default = "FALSE")] round_result: bool +) -> f64 { + let result = x * multiplier; + if round_result { + result.round() + } else { + result + } +} +``` + +```{r} +# All defaults +multiply(5.5) + +# Custom multiplier +multiply(5.5, multiplier = 2.5) + +# Custom multiplier and rounding +multiply(5.5, multiplier = 2.5, round_result = TRUE) +``` + +## Important notes + +- The value in `default = "..."` is inserted directly into the R function signature, so it must be valid R code +- You can use any R expression, including function calls: `#[extendr(default = "getOption('my_option')")]` +- The default values only affect the R wrapper—your Rust function still receives whatever value the R user provides (or the default if they don't)