From d08dc95b4f51eb72a8e2f21b6eebec93e81ce179 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:05:28 -0600 Subject: [PATCH 01/20] update project yaml --- _quarto.yml | 128 ++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/_quarto.yml b/_quarto.yml index d2edc48..bc49de2 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -14,20 +14,27 @@ website: repo-url: https://github.com/extendr/extendr.github.io repo-branch: main repo-actions: [edit, issue] + favicon: "/images/extendr-gears-logo.png" + page-navigation: true + bread-crumbs: true navbar: - logo: /images/feRris.png + search: true left: - text: "Get Started" href: get-started.qmd - text: Documentation menu: - - text: "Rust crates" - href: "https://extendr.github.io/extendr/" + - text: "{extendr} crates" + href: https://extendr.github.io/extendr/ - text: "{rextendr} package" href: https://extendr.github.io/rextendr/dev - - user-guide/index.qmd - - intro-rust/index.qmd + - text: "Rust Basics" + href: intro-rust/index.qmd + - text: "User Guide" + href: user-guide/index.qmd + - text: "Contributing" + href: contributing/index.qmd - text: "Blog" href: blog/index.qmd right: @@ -37,66 +44,71 @@ website: href: https://discord.gg/7hmApuc sidebar: - - title: "Rust for R Developers" - style: "floating" - collapse-level: 1 + - id: rust-basics + title: "Rust Basics" + collapse-level: 2 contents: - - intro-rust/index.qmd - - intro-rust/why-rust.qmd - - intro-rust/hello-world.qmd - - intro-rust/types.qmd - - intro-rust/fizz-buzz.qmd - - intro-rust/collections.qmd - - intro-rust/for-loops.qmd - - intro-rust/mutability.qmd - - intro-rust/mutable-vectors.qmd - - intro-rust/is-odd.qmd - - intro-rust/references-slices.qmd - - intro-rust/iterators.qmd - - intro-rust/iter-map.qmd - - intro-rust/structs.qmd - - intro-rust/struct-methods.qmd - - intro-rust/enums.qmd - - intro-rust/options.qmd + - section: "Rust Basics" + href: intro-rust/index.qmd + contents: + - intro-rust/hello-world.qmd + - intro-rust/types.qmd + - intro-rust/control-flow.qmd + - intro-rust/collections.qmd + - intro-rust/loops.qmd + - intro-rust/mutability.qmd + - intro-rust/functions.qmd + - intro-rust/ownership.qmd + - intro-rust/iterators.qmd + - intro-rust/structures.qmd + - intro-rust/implementations.qmd + - intro-rust/enumerations.qmd - - title: "User Guide" - collapse-level: 1 - style: "floating" + - id: guide + title: "User Guide" + collapse-level: 3 contents: - - user-guide/index.qmd - - user-guide/complete-example.qmd - - section: "R Packages" + - section: "User Guide" + href: user-guide/index.qmd contents: - - user-guide/r-pkgs/package-setup.qmd - - user-guide/r-pkgs/package-structure.qmd - - section: "Type Mapping" - contents: - - user-guide/type-mapping/extendr-macro.qmd - - user-guide/type-mapping/scalars.qmd - - user-guide/type-mapping/vectors.qmd - - user-guide/type-mapping/collections.qmd - - user-guide/type-mapping/into-list.qmd - - user-guide/type-mapping/missing-values.qmd - - user-guide/type-mapping/characters.qmd - - section: "Error Handling" + - user-guide/complete-example.qmd + - section: "R Packages" + contents: + - user-guide/package-setup.qmd + - user-guide/package-structure.qmd + - section: "CRAN" + + - id: contributing + title: "Contributing" + collapse-level: 2 + contents: + - section: "Contributing" + href: contributing/index.qmd 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 - - text: CRAN's MSRV - href: user-guide/cran-msrv.qmd - - text: WebR - href: user-guide/webr.qmd - - text: FAQ - href: user-guide/faq.qmd + - contributing/writing-code.qmd + - contributing/documenting-code.qmd + - contributing/testing-code.qmd + - contributing/scaffolding.qmd + - contributing/colors-and-fonts.qmd format: html: html-table-processing: none - theme: css/_bootswatch.scss + theme: + dark: [darkly, css/extendr.scss] + light: [flatly, css/extendr.scss] toc: true - include-in-header: - - text: | - + toc-location: right + grid: + sidebar-width: 250px + body-width: 900px + margin-width: 300px + +# this currently does not work +# see: https://github.com/quarto-dev/quarto-cli/issues/3157 +engine: knitr + +knitr: + opts_chunk: + collapse: true + comment: "#>" \ No newline at end of file From 0b0d91aa402a742107957bf4092f696524a02e32 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:05:36 -0600 Subject: [PATCH 02/20] add project variables --- _variables.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 _variables.yml diff --git a/_variables.yml b/_variables.yml new file mode 100644 index 0000000..4154425 --- /dev/null +++ b/_variables.yml @@ -0,0 +1,5 @@ +version: + r: "4.2" + rust: "1.65.0" + extendr: "0.8.1" + rextendr: "0.4.2.9000" From 6c3c53d571a1e3f5c1dd6b966c6fcc3c4a1b77fb Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:05:55 -0600 Subject: [PATCH 03/20] update top level pages --- get-started.qmd | 81 +++++++++++++++++-------- index.qmd | 153 ++++++++++-------------------------------------- 2 files changed, 89 insertions(+), 145 deletions(-) diff --git a/get-started.qmd b/get-started.qmd index c2b3397..a2eb0f0 100644 --- a/get-started.qmd +++ b/get-started.qmd @@ -3,48 +3,81 @@ title: "Get Started" toc: false --- -To build R packages with **extendr**, you need to have the right tools. - -## Step 1 [Install Rust]{.fw-light} - -::: clearfix -::: {.float-start style="width: 126px; margin-right: 0.8rem;"} -[![](images/cuddlyferris.svg)](https://www.rust-lang.org/tools/install) +::::::: card +:::::: {.row .g-0 .d-flex .flex-column .flex-md-row} +::: {.col-md-4 .d-flex .align-items-center .justify-content-center .p-3 .card-img-col} +[![](images/cuddlyferris.svg){width="96"}](https://www.rust-lang.org/tools/install) ::: +:::: col-md-8 +::: card-body +### Step 1 [Install Rust]{.fw-light} + Follow the [rustup installation instructions](https://www.rust-lang.org/tools/install) to install Rust. Note -that the current minimum supported Rust version (msrv) in **extendr** is `1.64`. -This is to ensure CRAN compliance. Windows users will also need to install the GNU toolchain as it matches Rtools. This can be done via rustup in the terminal: `rustup target add x86_64-pc-windows-gnu`. +that the current minimum supported Rust version (msrv) in **extendr** is +`{{< var version.rust >}}`. This is to ensure CRAN compliance. Windows users +will also need to install the GNU toolchain as it matches Rtools. This can be +done via rustup in the terminal: `rustup target add x86_64-pc-windows-gnu`. +::: +:::: +:::::: +::::::: + +::::::: {.card .mt-3 .border-0 .bg-transparent} +:::::: {.row .g-0 .d-flex .flex-column .flex-md-row} +::: {.col-md-4 .d-flex .align-items-center .justify-content-center .p-3 .card-img-col} +[![](images/Rlogo.svg){width="96"}](https://cran.r-project.org/) ::: -## Step 2 [Update R]{.fw-light} +:::: col-md-8 +::: card-body +### Step 2 [Update R]{.fw-light} -::: clearfix -::: {.float-start style="width: 126px; margin-right: 0.8rem;"} -[![](images/Rlogo.svg)](https://cran.r-project.org/) +We recommend using a moderately new version of R (\>= {{< var version.r >}}). +You can get that from [CRAN](https://cran.r-project.org/), but we highly +recommend that you use [rig](https://github.com/r-lib/rig) for this. With rig, +installing R is as simple as `rig add release`. ::: +:::: +:::::: +::::::: -We recommend using a moderately new version of R (\>= 4.2.0). You can get that -from [CRAN](https://cran.r-project.org/). +::::::: {.card .mt-3} +:::::: {.row .g-0 .d-flex .flex-column .flex-md-row} +::: {.col-md-4 .d-flex .align-items-center .justify-content-center .p-3 .card-img-col} +[![](images/rextendr-logo.png){width="96"}](https://extendr.github.io/rextendr/) ::: -## Step 3 [Install rextendr]{.fw-light} +:::: col-md-8 +::: card-body +### Step 3 [Install rextendr]{.fw-light} -::: clearfix -::: {.float-start style="width: 126px; margin-right: 0.8rem;"} -[![](images/rextendr-logo.png)](https://extendr.github.io/rextendr/) +The R package `{rextendr}` provides scaffolding for extendr projects and tools +for documenting Rust functions and objects. To install the latest release +version, use `install.packages("rextendr")`. For the latest development version, +use `pak::pak("extendr/rextendr")`. You can then run `rextendr::rust_sitrep()` +to check your Rust installation. ::: +:::: +:::::: +::::::: -The R package `{rextendr}` provides scaffolding for extendr projects and tools -for documenting Rust functions and objects. To install the latest development -version, use `pak::pak("extendr/rextendr")`. Note that **rextendr** provides a -function to check your Rust installation: `rextendr::rust_sitrep()`. +::::::: {.card .mt-3 .border-0 .bg-transparent} +:::::: {.row .g-0 .d-flex .flex-column .flex-md-row} +::: {.col-md-4 .d-flex .align-items-center .justify-content-center .p-3 .card-img-col} +[![](images/ra-logo-square.svg){width="96"}](https://rust-analyzer.github.io/) ::: -## Step 4 [Use rust-analyzer]{.fw-light} +:::: col-md-8 +::: card-body +### Step 4 [Use rust-analyzer]{.fw-light} This is optional, but strongly recommended. If you use [Visual Studio Code (VS Code)](https://code.visualstudio.com/download) or a similar IDE, the [rust-analyzer](https://rust-analyzer.github.io/) will provide you type hinting and auto-completion suggestions. It is **very** helpful! +::: +:::: +:::::: +::::::: \ No newline at end of file diff --git a/index.qmd b/index.qmd index a5e3ec2..ff3d71b 100644 --- a/index.qmd +++ b/index.qmd @@ -1,140 +1,51 @@ --- title: "" +toc: false +page-layout: full +anchor-sections: false --- -::: {style="margin:0; width: 100%;"} -::: {style="margin:0; width: 81%; float: left;"} +:::::: {#homepage-header .text-center .w-75 .mx-auto} +[Extending R with Rust]{.fs-3 .text-silver-4} -# extendr - extending R with Rust πŸ¦€ +:::{.fs-1 .mx-auto style="width: 80%;"} +Build blazingly fast R packages with developer-friendly and CRAN-ready tools. +::: -[![Github Actions Build -Status](https://github.com/extendr/extendr/workflows/Tests/badge.svg)](https://github.com/extendr/extendr/actions) +::: {.mt-1} +[![Github Actions Build Status](https://github.com/extendr/extendr/workflows/Tests/badge.svg)](https://github.com/extendr/extendr/actions) [![Crates.io](https://img.shields.io/crates/v/extendr-api.svg)](https://crates.io/crates/extendr-api) [![Documentation](https://docs.rs/extendr-api/badge.svg)](https://docs.rs/extendr-api) -[![License: -MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - - -::: - -::: {style="margin: 0.25rem 0 0 0; width: 19%; float: right;"} -![](images/extendr-logo-256.png){width="100%" fig-alt="rextendr logo"} +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ::: +:::::: -::: - - - -Build blazingly fast R packages πŸ“¦ with ease. The extendr ecosystem is **developer-friendly** and **CRAN**-ready. extendr let's you write ergonomic and idomatic Rust without having to worry about R's internals (much πŸ˜…). - -## Getting Started - -- Don't have Rust installed yet? [Start here](/get-started.qmd). -- Familiar with Rust and want to get your hands dirty? Follow along with a [complete example](user-guide/complete-example.qmd). - - -## The ecosystem - -**Rust crates** πŸ¦€ : - -- [`extendr-api`](https://extendr.github.io/extendr/extendr_api)β€”ergonomic, opinionated, and safe interface between R and Rust -- [`extendr-engine`](https://extendr.github.io/extendr/extendr_engine)β€”embed and use R in Rust -- [`extendr-ffi`](https://extendr.github.io/extendr/extendr_ffi)β€”hand-crafted bindings to R's C-API - -**R packages** πŸ“¦ : - -- [`{rextendr}`](https://extendr.github.io/rextendr/dev)β€”A `{usethis}`-like package that scaffolds extendr-powered R packages - +:::: {#homepage-cards .grid style="margin-top: 4rem;"} +::: {.g-col-12 .g-col-md-4 .card .p-4} +### Get Started {{< iconify openmoji:rocket >}} -## Quickstart +Build your first Rust-powered R package in minutes. Follow our step-by-step +guide or dive straight into a complete example. -We recommend using the development version of `{rextendr}` from GitHub. - -```r -# Install from CRAN -install.packages("rextendr") - -# Development version -remotes::install_github("extendr/rextendr") - -# create a new R package -usethis::create_package("helloextendr") - -# Use extendr -rextendr::use_extendr() - -# Document and build the package -rextendr::document() - -# run hello_world() -hello_world() -#> [1] "Hello, world!" -``` - -### Example: calling structs from R - -``` rust -use extendr_api::prelude::*; - -#[extendr] -#[derive(Debug)] -struct Person { - pub name: String, -} - -#[extendr] -impl Person { - fn new() -> Self { - Self { name: "".to_string() } - } - - fn set_name(&mut self, name: &str) { - self.name = name.to_string(); - } - - fn name(&self) -> &str { - self.name.as_str() - } -} - -#[extendr] -fn my_function() { } - -// Macro to generate exports -extendr_module! { - mod classes; - impl Person; - fn my_function; -} -``` - -The `#[extendr]` attribute causes the compiler to generate wrapper and -registration functions for R which are called when the package is loaded, thus -allowing one to access Rust functions and structures in an R session: +[Installation β†’](/get-started.qmd) [β€’]{.text-silver-3} [User Guide β†’](/user-guide/index.qmd) +::: -``` r -# call function -my_function() +::: {.g-col-12 .g-col-md-4 .card .p-4} +### See What's Possible {{< iconify openmoji:sparkler >}} -# create Person object -p <- Person$new() -p$set_name("foo") -p$name() # "foo" is returned -``` +Explore R packages built with extendr. From high-performance data processing to +system integrations, see what the community has built. -This, of course, is just the tip of the iceberg, for there are many ways to use -extendr in R: +[Browse awesome-extendr β†’](https://github.com/extendr/awesome-extendr) +::: -- In an interactive R session one may use [`rextendr::rust_function` and - friends](https://extendr.github.io/rextendr/reference/rust_source.html) to - quickly prototype Rust code. +::: {.g-col-12 .g-col-md-4 .card .p-4} +### Join the Community {{< iconify fluent-color:people-interwoven-16 >}} -- In an R package context, one may use - [`rextendr::use_extendr()`](https://extendr.github.io/rextendr/reference/use_extendr.html) - to setup a Rust powered R-package. See also the [vignette on - R-packages](https://extendr.github.io/rextendr/articles/package.html). +extendr has a welcoming and active community of R and Rust developers. Come ask +questions, share your work, or just hang out. -- It is also possible to inline Rust code in `Quarto` documents, see - [vignette on extendr - `knitr-engine`](https://extendr.github.io/rextendr/articles/rmarkdown.html). +[Discord β†’](https://discord.gg/7hmApuc) [β€’]{.text-silver-3} [GitHub β†’](https://github.com/extendr/extendr) +::: +:::: From 9e34f8fd6251d33eeddf06b7385edfda1b64df0f Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:07:11 -0600 Subject: [PATCH 04/20] use iconify extension --- _extensions/mcanouil/iconify/LICENSE | 21 + _extensions/mcanouil/iconify/_extension.yml | 7 + .../mcanouil/iconify/_modules/utils.lua | 655 ++++++++++++++++++ _extensions/mcanouil/iconify/_schema.yml | 145 ++++ _extensions/mcanouil/iconify/_snippets.json | 17 + .../mcanouil/iconify/iconify-icon.min.js | 13 + _extensions/mcanouil/iconify/iconify.lua | 265 +++++++ 7 files changed, 1123 insertions(+) create mode 100644 _extensions/mcanouil/iconify/LICENSE create mode 100644 _extensions/mcanouil/iconify/_extension.yml create mode 100644 _extensions/mcanouil/iconify/_modules/utils.lua create mode 100644 _extensions/mcanouil/iconify/_schema.yml create mode 100644 _extensions/mcanouil/iconify/_snippets.json create mode 100644 _extensions/mcanouil/iconify/iconify-icon.min.js create mode 100644 _extensions/mcanouil/iconify/iconify.lua diff --git a/_extensions/mcanouil/iconify/LICENSE b/_extensions/mcanouil/iconify/LICENSE new file mode 100644 index 0000000..4b53bb8 --- /dev/null +++ b/_extensions/mcanouil/iconify/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 MickaΓ«l Canouil + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/_extensions/mcanouil/iconify/_extension.yml b/_extensions/mcanouil/iconify/_extension.yml new file mode 100644 index 0000000..72eca88 --- /dev/null +++ b/_extensions/mcanouil/iconify/_extension.yml @@ -0,0 +1,7 @@ +title: Iconify +author: MickaΓ«l Canouil +version: 3.1.0 +quarto-required: ">=1.5.57" +contributes: + shortcodes: + - iconify.lua diff --git a/_extensions/mcanouil/iconify/_modules/utils.lua b/_extensions/mcanouil/iconify/_modules/utils.lua new file mode 100644 index 0000000..4ec88f1 --- /dev/null +++ b/_extensions/mcanouil/iconify/_modules/utils.lua @@ -0,0 +1,655 @@ +--- MC Utils - Common utility functions for Quarto Lua filters and shortcodes +--- @module utils +--- @license MIT +--- @copyright 2026 MickaΓ«l Canouil +--- @author MickaΓ«l Canouil +--- @version 1.0.0 + +local M = {} + +-- ============================================================================ +-- STRING UTILITIES +-- ============================================================================ + +--- Pandoc utility function for converting values to strings +--- @type function +M.stringify = pandoc.utils.stringify + +--- Check if a string is empty or nil. +--- Utility function to determine if a value is empty or nil, +--- which is useful for parameter validation throughout the module. +--- @param s string|nil|table The value to check for emptiness +--- @return boolean True if the value is nil or empty, false otherwise +--- @usage local result = M.is_empty("") -- returns true +--- @usage local result = M.is_empty(nil) -- returns true +--- @usage local result = M.is_empty("hello") -- returns false +function M.is_empty(s) + return s == nil or s == '' +end + +--- Escape special pattern characters in a string for Lua pattern matching +--- @param s string The string to escape +--- @return string The escaped string +--- @usage local escaped = M.escape_pattern("user/repo#123") +function M.escape_pattern(s) + local escaped = s:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") + return escaped +end + +--- Split a string by a separator +--- @param str string The string to split +--- @param sep string The separator pattern +--- @return table Array of string fields +--- @usage local parts = M.split("a.b.c", ".") +function M.split(str, sep) + local fields = {} + local pattern = string.format("([^%s]+)", sep) + str:gsub(pattern, function(c) fields[#fields + 1] = c end) + return fields +end + +--- Trim leading and trailing whitespace from a string +--- @param str string The string to trim +--- @return string The trimmed string +--- @usage local trimmed = M.trim(" hello world ") -- returns "hello world" +function M.trim(str) + if str == nil then return '' end + return str:match('^%s*(.-)%s*$') +end + +--- Convert any value to a string, handling Pandoc objects and empty values. +--- Returns nil for empty or nil values, otherwise returns a string representation. +--- @param val any The value to convert +--- @return string|nil The string value or nil if empty +--- @usage local str = M.to_string(kwargs.value) +function M.to_string(val) + if not val then return nil end + if type(val) == 'string' then + return val ~= '' and val or nil + end + -- Handle Pandoc objects + if pandoc and pandoc.utils and pandoc.utils.stringify then + local str = pandoc.utils.stringify(val) + return str ~= '' and str or nil + end + local str = tostring(val) + return str ~= '' and str or nil +end + +--- Escape special LaTeX characters in text. +--- @param text string The text to escape +--- @return string The escaped text safe for LaTeX +function M.escape_latex(text) + text = string.gsub(text, "\\", "\\textbackslash{}") + text = string.gsub(text, "%{", "\\{") + text = string.gsub(text, "%}", "\\}") + text = string.gsub(text, "%$", "\\$") + text = string.gsub(text, "%&", "\\&") + text = string.gsub(text, "%%", "\\%%") + text = string.gsub(text, "%#", "\\#") + text = string.gsub(text, "%^", "\\textasciicircum{}") + text = string.gsub(text, "%_", "\\_") + text = string.gsub(text, "~", "\\textasciitilde{}") + return text +end + +--- Escape special Typst characters in text. +--- @param text string The text to escape +--- @return string The escaped text safe for Typst +function M.escape_typst(text) + text = string.gsub(text, "%#", "\\#") + return text +end + +--- Escape special Lua pattern characters for use in string.gsub. +--- @param text string The text containing characters to escape +--- @return string The escaped text safe for Lua patterns +function M.escape_lua_pattern(text) + text = string.gsub(text, "%%", "%%%%") + text = string.gsub(text, "%^", "%%^") + text = string.gsub(text, "%$", "%%$") + text = string.gsub(text, "%(", "%%(") + text = string.gsub(text, "%)", "%%)") + text = string.gsub(text, "%.", "%%.") + text = string.gsub(text, "%[", "%%[") + text = string.gsub(text, "%]", "%%]") + text = string.gsub(text, "%*", "%%*") + text = string.gsub(text, "%+", "%%+") + text = string.gsub(text, "%-", "%%-") + text = string.gsub(text, "%?", "%%?") + return text +end + +--- Escape special HTML characters in text. +--- Escapes &, <, >, ", and ' to prevent XSS and ensure valid HTML. +--- @param text string The text to escape +--- @return string Escaped text safe for use in HTML +--- @usage local escaped = M.escape_html('Hello ') +function M.escape_html(text) + if text == nil then return '' end + if type(text) ~= 'string' then text = tostring(text) end + local result = text + :gsub('&', '&') + :gsub('<', '<') + :gsub('>', '>') + :gsub('"', '"') + :gsub("'", ''') + return result +end + +--- Escape special HTML attribute characters. +--- Escapes characters that could break attribute values. +--- @param value string The attribute value to escape +--- @return string Escaped value safe for use in HTML attributes +--- @usage local escaped = M.escape_attribute('Hello "World"') +function M.escape_attribute(value) + if value == nil then return '' end + if type(value) ~= 'string' then value = tostring(value) end + local result = value + :gsub('&', '&') + :gsub('"', '"') + :gsub('<', '<') + :gsub('>', '>') + return result +end + +--- Escape text for different formats. +--- @param text string The text to escape +--- @param format string The format to escape for (e.g., "latex", "typst", "lua") +--- @return string The escaped text +function M.escape_text(text, format) + local escape_functions = { + latex = M.escape_latex, + typst = M.escape_typst, + lua = M.escape_lua_pattern + } + + local escape = escape_functions[format] + if escape then + return escape(text) + else + error("Unsupported escape format: " .. format) + end +end + +--- Converts a string to a valid HTML id by lowercasing and replacing spaces. +--- @param text string The text to convert +--- @return string The HTML id +function M.ascii_id(text) + local id = text:lower():gsub("[^a-z0-9 ]", ""):gsub(" +", "-") + return id +end + +-- ============================================================================ +-- METADATA UTILITIES +-- ============================================================================ + +--- Get configuration from extensions.mcanouil namespace. +--- @param meta table Document metadata +--- @param key string The key to retrieve +--- @return any The value or nil +--- @usage local value = M.get_mcanouil_config(meta, 'section-outline') +function M.get_mcanouil_config(meta, key) + local mcanouil_ext = meta.extensions and meta.extensions.mcanouil + if not mcanouil_ext then return nil end + return mcanouil_ext[key] +end + +--- Get a section config table from extensions.mcanouil.{section}. +--- @param meta table Document metadata +--- @param section string The section name (e.g., 'code-window', 'typst-markdown') +--- @return table|nil The section config table or nil +--- @usage local config = M.get_mcanouil_section(meta, 'code-window') +function M.get_mcanouil_section(meta, section) + local mcanouil_ext = meta.extensions and meta.extensions.mcanouil + if not mcanouil_ext then return nil end + return mcanouil_ext[section] +end + +--- Extract metadata value from document meta using nested structure. +--- Supports the extensions.{extension-name}.{key} pattern. +--- @param meta table The document metadata table +--- @param extension_name string The extension name (e.g., "github", "iconify") +--- @param key string The metadata key to retrieve +--- @return string|nil The metadata value as a string, or nil if not found +--- @usage local repo = M.get_metadata_value(meta, "github", "repository-name") +function M.get_metadata_value(meta, extension_name, key) + if meta['extensions'] and meta['extensions'][extension_name] and meta['extensions'][extension_name][key] then + return M.stringify(meta['extensions'][extension_name][key]) + end + return nil +end + +--- Check for deprecated top-level configuration and emit warning +--- @param meta table The document metadata table +--- @param extension_name string The extension name +--- @param key string|nil The configuration key being accessed (nil to check entire extension config) +--- @param deprecation_warning_shown boolean Flag to track if warning has been shown +--- @return any|nil The value from deprecated config, or nil if not found +--- @return boolean Updated deprecation warning flag +function M.check_deprecated_config(meta, extension_name, key, deprecation_warning_shown) + -- Handle array-based configuration (when key is nil) + if key == nil then + if not M.is_empty(meta[extension_name]) then + if not deprecation_warning_shown then + M.log_warning( + extension_name, + 'Top-level "' .. extension_name .. '" configuration is deprecated. ' .. + 'Please use:\n' .. + 'extensions:\n' .. + ' ' .. extension_name .. ':\n' .. + ' - (configuration array)' + ) + deprecation_warning_shown = true + end + return meta[extension_name], deprecation_warning_shown + end + return nil, deprecation_warning_shown + end + + -- Handle key-value configuration (original behaviour) + if not M.is_empty(meta[extension_name]) and not M.is_empty(meta[extension_name][key]) then + if not deprecation_warning_shown then + M.log_warning( + extension_name, + 'Top-level "' .. extension_name .. '" configuration is deprecated. ' .. + 'Please use:\n' .. + 'extensions:\n' .. + ' ' .. extension_name .. ':\n' .. + ' ' .. key .. ': value' + ) + deprecation_warning_shown = true + end + return M.stringify(meta[extension_name][key]), deprecation_warning_shown + end + return nil, deprecation_warning_shown +end + +-- ============================================================================ +-- PANDOC/QUARTO FORMAT UTILITIES +-- ============================================================================ + +--- Create a Pandoc Link element +--- @param text string|nil The link text +--- @param uri string|nil The URI to link to +--- @return pandoc.Link|nil A Pandoc Link element or nil if text or uri is empty +function M.create_link(text, uri) + if not M.is_empty(uri) and not M.is_empty(text) then + return pandoc.Link({ pandoc.Str(text --[[@as string]]) }, uri --[[@as string]]) + end + return nil +end + +--- Helper to build Pandoc attributes +--- @param id string|nil Element ID +--- @param classes table|nil List of CSS classes +--- @param attributes table|nil Key-value attributes +--- @return pandoc.Attr Pandoc Attr object +function M.attr(id, classes, attributes) + return pandoc.Attr(id or '', classes or {}, attributes or {}) +end + +--- Check if a class list contains a specific class name +--- @param classes table|nil List of CSS classes +--- @param name string The class name to search for +--- @return boolean True if the class is found, false otherwise +function M.has_class(classes, name) + if not classes then return false end + for _, cls in ipairs(classes) do + if cls == name then return true end + end + return false +end + +--- Add a class to the class list if it doesn't already exist +--- @param classes table List of CSS classes +--- @param name string The class name to add +function M.add_class(classes, name) + if not M.has_class(classes, name) then + table.insert(classes, name) + end +end + +--- Retrieve the current Quarto output format. +--- @return string The output format ("pptx", "html", "latex", "typst", "docx", or "unknown") +--- @return string The language of the output format +function M.get_quarto_format() + if quarto.doc.is_format("html:js") then + return "html", "html" + elseif quarto.doc.is_format("latex") then + return "latex", "latex" + elseif quarto.doc.is_format("typst") then + return "typst", "typst" + elseif quarto.doc.is_format("docx") then + return "docx", "openxml" + elseif quarto.doc.is_format("pptx") then + return "pptx", "openxml" + else + return "unknown", "unknown" + end +end + +-- ============================================================================ +-- OBJECT/TABLE UTILITIES +-- ============================================================================ + +--- Check if an object (including tables and lists) is empty or nil +--- @param obj any The object to check +--- @return boolean true if the object is nil, empty string, or empty table/list +function M.is_object_empty(obj) + local function length(x) + local count = 0 + if x ~= nil then + for _ in pairs(x) do + count = count + 1 + end + end + return count + end + if pandoc.utils.type(obj) == "table" or pandoc.utils.type(obj) == "List" then + return obj == nil or obj == '' or length(obj) == 0 + else + return obj == nil or obj == '' + end +end + +--- Check if an object is a simple type (string, number, or boolean) +--- @param obj any The object to check +--- @return boolean true if the object is a string, number, or boolean +function M.is_type_simple(obj) + return pandoc.utils.type(obj) == "string" or pandoc.utils.type(obj) == "number" or pandoc.utils.type(obj) == "boolean" +end + +--- Check if an object is a function or userdata +--- @param obj any The object to check +--- @return boolean true if the object is a function or userdata +function M.is_function_userdata(obj) + return pandoc.utils.type(obj) == "function" or pandoc.utils.type(obj) == "userdata" +end + +--- Get nested value from object using field path +--- @param fields table Array of field names to traverse +--- @param obj table The object to extract value from +--- @return any The value at the nested path +--- @usage local val = M.get_value({"a", "b", "c"}, obj) +function M.get_value(fields, obj) + local value = obj + for _, field in ipairs(fields) do + value = value[field] + end + return value +end + +--- Convert Pandoc AttributeList to plain table for easier processing. +--- @param element table Element with attributes field (Div, Span, Table, Image) +--- @return table Plain table with attribute key-value pairs +function M.attributes_to_table(element) + local attrs = {} + for k, v in pairs(element.attributes) do + attrs[k] = v + end + return attrs +end + +-- ============================================================================ +-- HTML RAW GENERATION UTILITIES +-- ============================================================================ + +--- Generates a raw HTML header element. +--- @param level integer The header level (e.g., 2 for

) +--- @param text string|nil The header text +--- @param id string The id attribute for the header +--- @param classes table List of classes for the header +--- @param attributes table|nil Additional HTML attributes +--- @return string Raw HTML string for the header +function M.raw_header(level, text, id, classes, attributes) + local attr_str = '' + if id and id ~= '' then + attr_str = attr_str .. ' id="' .. M.escape_attribute(id) .. '"' + end + if classes and #classes > 0 then + local escaped_classes = {} + for i, cls in ipairs(classes) do + escaped_classes[i] = M.escape_attribute(cls) + end + attr_str = attr_str .. ' class="' .. table.concat(escaped_classes, ' ') .. '"' + end + if attributes then + for k, v in pairs(attributes) do + attr_str = attr_str .. ' ' .. M.escape_attribute(k) .. '="' .. M.escape_attribute(v) .. '"' + end + end + return string.format('%s', level, attr_str, M.escape_html(text or ''), level) +end + +-- ============================================================================ +-- HTML DEPENDENCY UTILITIES +-- ============================================================================ + +--- Managed HTML dependency tracker +--- Tracks which dependencies have been added to prevent duplication +--- @type table +local dependency_tracker = {} + +--- Ensure HTML dependency is added only once per document. +--- Prevents duplicate dependency injection by tracking dependencies by name. +--- Returns true if dependency was added, false if already present. +--- +--- @param config table Dependency configuration with fields: name (required), version, scripts, stylesheets, head +--- @return boolean True if dependency was added, false if already added +--- @usage M.ensure_html_dependency({name = 'my-lib', version = '1.0.0', scripts = {'lib.js'}}) +function M.ensure_html_dependency(config) + if not config or not config.name then + error("HTML dependency configuration must include a 'name' field") + end + + --- @type string Unique key for this dependency + local dep_key = config.name + + -- Check if already added + if dependency_tracker[dep_key] then + return false + end + + -- Add the dependency + quarto.doc.add_html_dependency(config) + + -- Mark as added + dependency_tracker[dep_key] = true + return true +end + +--- Reset dependency tracker. +--- Useful for testing or when processing multiple independent documents. +--- In normal usage, this should not be called as dependencies persist per document. +--- +--- @return nil +function M.reset_dependencies() + dependency_tracker = {} +end + +-- ============================================================================ +-- ENHANCED METADATA/CONFIGURATION UTILITIES +-- ============================================================================ + +--- Get option value with fallback hierarchy: args β†’ extensions.{extension}.{key} β†’ defaults. +--- Provides a standardised way to read configuration values with multiple fallback levels. +--- Priority: 1. Named arguments (kwargs), 2. Document metadata, 3. Default values. +--- +--- @param spec table Configuration spec with fields: extension (string), key (string), args (table|nil), meta (table|nil), default (any|nil) +--- @return any The resolved option value (type depends on what's stored in config) +--- @usage local duration = M.get_option_with_fallbacks({extension = 'animate', key = 'duration', args = kwargs, meta = meta, default = '3s'}) +function M.get_option_with_fallbacks(spec) + -- Validate required fields + if not spec.extension or not spec.key then + error("Configuration spec must include 'extension' and 'key' fields") + end + + --- @type string The extension name + local extension = spec.extension + --- @type string The configuration key + local key = spec.key + --- @type table|nil Named arguments table + local args = spec.args + --- @type table|nil Document metadata + local meta = spec.meta + --- @type any Default value if not found elsewhere + local default = spec.default + + -- Priority 1: Check named arguments (kwargs) + if args and args[key] then + local arg_value = M.stringify(args[key]) + if not M.is_empty(arg_value) then + return arg_value + end + end + + -- Priority 2: Check metadata extensions.{extension}.{key} + if meta then + local meta_value = M.get_metadata_value(meta, extension, key) + if not M.is_empty(meta_value) then + return meta_value + end + end + + -- Priority 3: Return default value + return default +end + +--- Get multiple option values at once with fallback hierarchy. +--- Batch version of get_option_with_fallbacks for retrieving multiple configuration values. +--- Returns a table mapping each key to its resolved value. +--- +--- @param spec table Configuration spec with fields: extension (string), keys (table), args (table|nil), meta (table|nil), defaults (table|nil) +--- @return table Table mapping each key to its resolved value +--- @usage local opts = M.get_options({extension = 'animate', keys = {'duration', 'delay'}, args = kwargs, meta = meta, defaults = {duration = '3s', delay = '2s'}}) +function M.get_options(spec) + -- Validate required fields + if not spec.extension or not spec.keys then + error("Configuration spec must include 'extension' and 'keys' fields") + end + + --- @type table Result table + local result = {} + + --- @type table Default values table + local defaults = spec.defaults or {} + + -- Get each key using the single-option fallback logic + for _, key in ipairs(spec.keys) do + result[key] = M.get_option_with_fallbacks({ + extension = spec.extension, + key = key, + args = spec.args, + meta = spec.meta, + default = defaults[key] + }) + end + + return result +end + +-- ============================================================================ +-- LOGGING UTILITIES +-- ============================================================================ + +--- Format and log an error message with extension prefix. +--- Provides standardised error messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "external", "lua-env") +--- @param message string The error message to display +--- @usage M.log_error("external", "Could not open file 'example.md'.") +function M.log_error(extension_name, message) + quarto.log.error("[" .. extension_name .. "] " .. message) +end + +--- Format and log a warning message with extension prefix. +--- Provides standardised warning messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "external", "lua-env") +--- @param message string The warning message to display +--- @usage M.log_warning("lua-env", "No variable name provided.") +function M.log_warning(extension_name, message) + quarto.log.warning("[" .. extension_name .. "] " .. message) +end + +--- Format and log an output message with extension prefix. +--- Provides standardised informational messages with consistent formatting across extensions. +--- Format: [extension-name] Message with details. +--- +--- @param extension_name string The name of the extension (e.g., "lua-env") +--- @param message string The informational message to display +--- @usage M.log_output("lua-env", "Exported metadata to: output.json") +function M.log_output(extension_name, message) + quarto.log.output("[" .. extension_name .. "] " .. message) +end + +-- ============================================================================ +-- PATH UTILITIES +-- ============================================================================ + +--- Resolve a path relative to the project directory. +--- If the path starts with `/`, it is treated as relative to the project directory. +--- If `quarto.project.directory` is available, it is prepended to the path. +--- If `quarto.project.directory` is nil, the leading `/` is removed. +--- @param path string The path to resolve (may start with `/`) +--- @return string The resolved path +--- @usage local resolved = M.resolve_project_path("/config.yml") +--- @usage local resolved = M.resolve_project_path("config.yml") +function M.resolve_project_path(path) + if M.is_empty(path) then + return path + end + + if path:sub(1, 1) == "/" then + if quarto.project.directory then + -- Prepend project directory to absolute path + return quarto.project.directory .. path + else + -- Remove leading `/` if no project directory + return path:sub(2) + end + else + return path + end +end + +-- ============================================================================ +-- COLOUR UTILITIES +-- ============================================================================ + +--- Get colour value from attributes table, accepting both British and American spellings. +--- Checks for 'colour' first (British, primary), then falls back to 'color' (American). +--- @param attrs table Attributes table (kwargs or element.attributes) +--- @param default string|nil Default value if neither spelling is found +--- @return string|nil Colour value or default +--- @usage local colour = M.get_colour(kwargs, 'info') +function M.get_colour(attrs, default) + if attrs == nil then + return default + end + local value = attrs.colour or attrs.color + if M.is_empty(value) then + return default + end + return M.stringify(value) +end + +--- Check if a colour value is a custom colour (hex, rgb, hsl, etc.). +--- Used to determine whether to apply a semantic class or inline style. +--- @param colour string|nil The colour value to check +--- @return boolean True if it's a custom colour value +--- @usage local is_custom = M.is_custom_colour('#ff6600') -- returns true +function M.is_custom_colour(colour) + if not colour then return false end + local str = colour:lower() + return str:match('^#') or str:match('^rgb') or str:match('^hsl') +end + +-- ============================================================================ +-- MODULE EXPORT +-- ============================================================================ + +return M diff --git a/_extensions/mcanouil/iconify/_schema.yml b/_extensions/mcanouil/iconify/_schema.yml new file mode 100644 index 0000000..1b62fb5 --- /dev/null +++ b/_extensions/mcanouil/iconify/_schema.yml @@ -0,0 +1,145 @@ +# Schema for the iconify extension + +$schema: https://m.canouil.dev/quarto-wizard/assets/schema/v1/extension-schema.json + +options: + set: + type: string + default: "octicon" + description: "Default icon set to use when not specified in the shortcode (e.g., 'mdi', 'fa-solid')." + size: + type: string + description: "Default icon size as a keyword or CSS value (e.g., 'large', '2x', '1.5em')." + width: + type: string + description: "Default icon width." + height: + type: string + description: "Default icon height." + flip: + type: string + description: "Default flip transformation for icons." + enum: + - horizontal + - vertical + - horizontal,vertical + rotate: + type: string + description: "Default rotation for icons (e.g., '90deg', '180deg', '1')." + style: + type: string + description: "Default additional inline CSS styles applied to all icons." + inline: + type: boolean + default: true + description: "Whether to render icons inline by default." + mode: + type: string + description: "Default rendering mode for icons." + enum: + - svg + - style + - bg + - mask + +# Per-shortcode schemas. +# Describes positional arguments and named attributes for {{< iconify >}} and {{< quarto >}}. +shortcodes: + iconify: + description: "Renders an Iconify icon. Accepts 'set:icon' or 'set icon' syntax." + arguments: + - name: set-or-icon + type: string + required: true + description: "Icon set name, or 'set:icon' combined format." + - name: icon + type: string + description: "Icon name when the first argument is the set name." + attributes: + size: + type: string + description: "Icon size as a keyword or CSS value (e.g., 'large', '2x', '1.5em')." + width: + type: string + description: "Icon width." + height: + type: string + description: "Icon height." + flip: + type: string + description: "Flip transformation for the icon." + enum: + - horizontal + - vertical + - horizontal,vertical + rotate: + type: string + description: "Rotation for the icon (e.g., '90deg', '180deg', '1')." + inline: + type: string + description: "Whether to render the icon inline." + enum: + - "true" + - "false" + mode: + type: string + description: "Rendering mode for the icon." + enum: + - svg + - style + - bg + - mask + style: + type: string + description: "Additional inline CSS styles for the icon." + label: + type: string + description: "Accessible label for the icon (aria-label)." + title: + type: string + description: "Title attribute for the icon tooltip." + quarto: + description: "Renders the Quarto icon with default blue styling from the simple-icons set." + attributes: + size: + type: string + description: "Icon size as a keyword or CSS value." + width: + type: string + description: "Icon width." + height: + type: string + description: "Icon height." + flip: + type: string + description: "Flip transformation for the icon." + enum: + - horizontal + - vertical + - horizontal,vertical + rotate: + type: string + description: "Rotation for the icon (e.g., '90deg', '180deg', '1')." + inline: + type: string + description: "Whether to render the icon inline." + enum: + - "true" + - "false" + mode: + type: string + description: "Rendering mode for the icon." + enum: + - svg + - style + - bg + - mask + style: + type: string + description: "Additional inline CSS styles for the icon." + label: + type: string + description: "Accessible label for the icon." + title: + type: string + description: "Title attribute for the icon tooltip." diff --git a/_extensions/mcanouil/iconify/_snippets.json b/_extensions/mcanouil/iconify/_snippets.json new file mode 100644 index 0000000..e2fba87 --- /dev/null +++ b/_extensions/mcanouil/iconify/_snippets.json @@ -0,0 +1,17 @@ +{ + "Icon": { + "prefix": "iconify", + "body": "{{< iconify ${1:fluent-emoji}:${2:exploding-head} >}}", + "description": "Insert an Iconify icon using set:icon syntax" + }, + "Icon with size": { + "prefix": "iconify-size", + "body": "{{< iconify ${1:fluent-emoji}:${2:exploding-head} size=${3:1em} >}}", + "description": "Insert an Iconify icon with a size" + }, + "Icon with style": { + "prefix": "iconify-style", + "body": "{{< iconify ${1:simple-icons}:${2:quarto} style=\"color:${3:#74aadb};\" >}}", + "description": "Insert an Iconify icon with custom CSS style" + } +} diff --git a/_extensions/mcanouil/iconify/iconify-icon.min.js b/_extensions/mcanouil/iconify/iconify-icon.min.js new file mode 100644 index 0000000..2b9c109 --- /dev/null +++ b/_extensions/mcanouil/iconify/iconify-icon.min.js @@ -0,0 +1,13 @@ +/** +* (c) Iconify +* +* For the full copyright and license information, please view the license.txt +* files at https://github.com/iconify/iconify +* +* Licensed under MIT. +* Source: https://github.com/iconify/code/tree/gh-pages/iconify-icon +* +* @license MIT +* @version 3.0.2 +*/ +!function(){"use strict";const t=Object.freeze({left:0,top:0,width:16,height:16}),e=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),n=Object.freeze({...t,...e}),i=Object.freeze({...n,body:"",hidden:!1}),r=Object.freeze({width:null,height:null}),o=Object.freeze({...r,...e});const s=/[\s,]+/;const c={...o,preserveAspectRatio:""};function a(t){const e={...c},n=(e,n)=>t.getAttribute(e)||n;var i;return e.width=n("width",null),e.height=n("height",null),e.rotate=function(t,e=0){const n=t.replace(/^-?[0-9.]*/,"");function i(t){for(;t<0;)t+=4;return t%4}if(""===n){const e=parseInt(t);return isNaN(e)?0:i(e)}if(n!==t){let e=0;switch(n){case"%":e=25;break;case"deg":e=90}if(e){let r=parseFloat(t.slice(0,t.length-n.length));return isNaN(r)?0:(r/=e,r%1==0?i(r):0)}}return e}(n("rotate","")),i=e,n("flip","").split(s).forEach(t=>{switch(t.trim()){case"horizontal":i.hFlip=!0;break;case"vertical":i.vFlip=!0}}),e.preserveAspectRatio=n("preserveAspectRatio",n("preserveaspectratio","")),e}const u=/^[a-z0-9]+(-[a-z0-9]+)*$/,l=(t,e,n,i="")=>{const r=t.split(":");if("@"===t.slice(0,1)){if(r.length<2||r.length>3)return null;i=r.shift().slice(1)}if(r.length>3||!r.length)return null;if(r.length>1){const t=r.pop(),n=r.pop(),o={provider:r.length>0?r[0]:i,prefix:n,name:t};return e&&!f(o)?null:o}const o=r[0],s=o.split("-");if(s.length>1){const t={provider:i,prefix:s.shift(),name:s.join("-")};return e&&!f(t)?null:t}if(n&&""===i){const t={provider:i,prefix:"",name:o};return e&&!f(t,n)?null:t}return null},f=(t,e)=>!!t&&!(!(e&&""===t.prefix||t.prefix)||!t.name);function d(t,n){const r=function(t,e){const n={};!t.hFlip!=!e.hFlip&&(n.hFlip=!0),!t.vFlip!=!e.vFlip&&(n.vFlip=!0);const i=((t.rotate||0)+(e.rotate||0))%4;return i&&(n.rotate=i),n}(t,n);for(const o in i)o in e?o in t&&!(o in r)&&(r[o]=e[o]):o in n?r[o]=n[o]:o in t&&(r[o]=t[o]);return r}function h(t,e,n){const i=t.icons,r=t.aliases||Object.create(null);let o={};function s(t){o=d(i[t]||r[t],o)}return s(e),n.forEach(s),d(t,o)}function p(t,e){const n=[];if("object"!=typeof t||"object"!=typeof t.icons)return n;t.not_found instanceof Array&&t.not_found.forEach(t=>{e(t,null),n.push(t)});const i=function(t){const e=t.icons,n=t.aliases||Object.create(null),i=Object.create(null);return Object.keys(e).concat(Object.keys(n)).forEach(function t(r){if(e[r])return i[r]=[];if(!(r in i)){i[r]=null;const e=n[r]&&n[r].parent,o=e&&t(e);o&&(i[r]=[e].concat(o))}return i[r]}),i}(t);for(const r in i){const o=i[r];o&&(e(r,h(t,r,o)),n.push(r))}return n}const g={provider:"",aliases:{},not_found:{},...t};function b(t,e){for(const n in e)if(n in t&&typeof t[n]!=typeof e[n])return!1;return!0}function v(t){if("object"!=typeof t||null===t)return null;const e=t;if("string"!=typeof e.prefix||!t.icons||"object"!=typeof t.icons)return null;if(!b(t,g))return null;const n=e.icons;for(const t in n){const e=n[t];if(!t||"string"!=typeof e.body||!b(e,i))return null}const r=e.aliases||Object.create(null);for(const t in r){const e=r[t],o=e.parent;if(!t||"string"!=typeof o||!n[o]&&!r[o]||!b(e,i))return null}return e}const m=Object.create(null);function y(t,e){const n=m[t]||(m[t]=Object.create(null));return n[e]||(n[e]=function(t,e){return{provider:t,prefix:e,icons:Object.create(null),missing:new Set}}(t,e))}function x(t,e){return v(e)?p(e,(e,n)=>{n?t.icons[e]=n:t.missing.add(e)}):[]}function _(t,e){let n=[];return("string"==typeof t?[t]:Object.keys(m)).forEach(t=>{("string"==typeof t&&"string"==typeof e?[e]:Object.keys(m[t]||{})).forEach(e=>{const i=y(t,e);n=n.concat(Object.keys(i.icons).map(n=>(""!==t?"@"+t+":":"")+e+":"+n))})}),n}let w=!1;function k(t){return"boolean"==typeof t&&(w=t),w}function A(t){const e="string"==typeof t?l(t,!0,w):t;if(e){const t=y(e.provider,e.prefix),n=e.name;return t.icons[n]||(t.missing.has(n)?null:void 0)}}function j(t,e){const n=l(t,!0,w);if(!n)return!1;const i=y(n.provider,n.prefix);return e?function(t,e,n){try{if("string"==typeof n.body)return t.icons[e]={...n},!0}catch(t){}return!1}(i,n.name,e):(i.missing.add(n.name),!0)}function O(t,e){if("object"!=typeof t)return!1;if("string"!=typeof e&&(e=t.provider||""),w&&!e&&!t.prefix){let e=!1;return v(t)&&(t.prefix="",p(t,(t,n)=>{j(t,n)&&(e=!0)})),e}const n=t.prefix;return!!f({prefix:n,name:"a"})&&!!x(y(e,n),t)}function C(t){return!!A(t)}function I(t){const e=A(t);return e?{...n,...e}:e}function E(t,e){t.forEach(t=>{const n=t.loaderCallbacks;n&&(t.loaderCallbacks=n.filter(t=>t.id!==e))})}let T=0;const F=Object.create(null);function R(t,e){F[t]=e}function S(t){return F[t]||F[""]}function L(t){let e;if("string"==typeof t.resources)e=[t.resources];else if(e=t.resources,!(e instanceof Array&&e.length))return null;return{resources:e,path:t.path||"/",maxURL:t.maxURL||500,rotate:t.rotate||750,timeout:t.timeout||5e3,random:!0===t.random,index:t.index||0,dataAfterTimeout:!1!==t.dataAfterTimeout}}const P=Object.create(null),M=["https://api.simplesvg.com","https://api.unisvg.com"],N=[];for(;M.length>0;)1===M.length||Math.random()>.5?N.push(M.shift()):N.push(M.pop());function z(t,e){const n=L(e);return null!==n&&(P[t]=n,!0)}function Q(t){return P[t]}function q(){return Object.keys(P)}P[""]=L({resources:["https://api.iconify.design"].concat(N)});const U={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function D(t,e,n,i){const r=t.resources.length,o=t.random?Math.floor(Math.random()*r):t.index;let s;if(t.random){let e=t.resources.slice(0);for(s=[];e.length>1;){const t=Math.floor(Math.random()*e.length);s.push(e[t]),e=e.slice(0,t).concat(e.slice(t+1))}s=s.concat(e)}else s=t.resources.slice(o).concat(t.resources.slice(0,o));const c=Date.now();let a,u="pending",l=0,f=null,d=[],h=[];function p(){f&&(clearTimeout(f),f=null)}function g(){"pending"===u&&(u="aborted"),p(),d.forEach(t=>{"pending"===t.status&&(t.status="aborted")}),d=[]}function b(t,e){e&&(h=[]),"function"==typeof t&&h.push(t)}function v(){u="failed",h.forEach(t=>{t(void 0,a)})}function m(){d.forEach(t=>{"pending"===t.status&&(t.status="aborted")}),d=[]}function y(){if("pending"!==u)return;p();const i=s.shift();if(void 0===i)return d.length?void(f=setTimeout(()=>{p(),"pending"===u&&(m(),v())},t.timeout)):void v();const r={status:"pending",resource:i,callback:(e,n)=>{!function(e,n,i){const r="success"!==n;switch(d=d.filter(t=>t!==e),u){case"pending":break;case"failed":if(r||!t.dataAfterTimeout)return;break;default:return}if("abort"===n)return a=i,void v();if(r)return a=i,void(d.length||(s.length?y():v()));if(p(),m(),!t.random){const n=t.resources.indexOf(e.resource);-1!==n&&n!==t.index&&(t.index=n)}u="completed",h.forEach(t=>{t(i)})}(r,e,n)}};d.push(r),l++,f=setTimeout(y,t.rotate),n(i,e,r.callback)}return"function"==typeof i&&h.push(i),setTimeout(y),function(){return{startTime:c,payload:e,status:u,queriesSent:l,queriesPending:d.length,subscribe:b,abort:g}}}function H(t){const e={...U,...t};let n=[];function i(){n=n.filter(t=>"pending"===t().status)}return{query:function(t,r,o){const s=D(e,t,r,(t,e)=>{i(),o&&o(t,e)});return n.push(s),s},find:function(t){return n.find(e=>t(e))||null},setIndex:t=>{e.index=t},getIndex:()=>e.index,cleanup:i}}function J(){}const $=Object.create(null);function B(t,e,n){let i,r;if("string"==typeof t){const e=S(t);if(!e)return n(void 0,424),J;r=e.send;const o=function(t){if(!$[t]){const e=Q(t);if(!e)return;$[t]={config:e,redundancy:H(e)}}return $[t]}(t);o&&(i=o.redundancy)}else{const e=L(t);if(e){i=H(e);const n=S(t.resources?t.resources[0]:"");n&&(r=n.send)}}return i&&r?i.query(e,r,n)().abort:(n(void 0,424),J)}function G(){}function V(t){t.iconsLoaderFlag||(t.iconsLoaderFlag=!0,setTimeout(()=>{t.iconsLoaderFlag=!1,function(t){t.pendingCallbacksFlag||(t.pendingCallbacksFlag=!0,setTimeout(()=>{t.pendingCallbacksFlag=!1;const e=t.loaderCallbacks?t.loaderCallbacks.slice(0):[];if(!e.length)return;let n=!1;const i=t.provider,r=t.prefix;e.forEach(e=>{const o=e.icons,s=o.pending.length;o.pending=o.pending.filter(e=>{if(e.prefix!==r)return!0;const s=e.name;if(t.icons[s])o.loaded.push({provider:i,prefix:r,name:s});else{if(!t.missing.has(s))return n=!0,!0;o.missing.push({provider:i,prefix:r,name:s})}return!1}),o.pending.length!==s&&(n||E([t],e.id),e.callback(o.loaded.slice(0),o.missing.slice(0),o.pending.slice(0),e.abort))})}))}(t)}))}function K(t,e,n){function i(){const n=t.pendingIcons;e.forEach(e=>{n&&n.delete(e),t.icons[e]||t.missing.add(e)})}if(n&&"object"==typeof n)try{if(!x(t,n).length)return void i()}catch(t){console.error(t)}i(),V(t)}function W(t,e){t instanceof Promise?t.then(t=>{e(t)}).catch(()=>{e(null)}):e(t)}function X(t,e){t.iconsToLoad?t.iconsToLoad=t.iconsToLoad.concat(e).sort():t.iconsToLoad=e,t.iconsQueueFlag||(t.iconsQueueFlag=!0,setTimeout(()=>{t.iconsQueueFlag=!1;const{provider:e,prefix:n}=t,i=t.iconsToLoad;if(delete t.iconsToLoad,!i||!i.length)return;const r=t.loadIcon;if(t.loadIcons&&(i.length>1||!r))return void W(t.loadIcons(i,n,e),e=>{K(t,i,e)});if(r)return void i.forEach(i=>{W(r(i,n,e),e=>{K(t,[i],e?{prefix:n,icons:{[i]:e}}:null)})});const{valid:o,invalid:s}=function(t){const e=[],n=[];return t.forEach(t=>{(t.match(u)?e:n).push(t)}),{valid:e,invalid:n}}(i);if(s.length&&K(t,s,null),!o.length)return;const c=n.match(u)?S(e):null;c?c.prepare(e,n,o).forEach(n=>{B(e,n,e=>{K(t,n.icons,e)})}):K(t,o,null)}))}const Y=(t,e)=>{const n=function(t){const e={loaded:[],missing:[],pending:[]},n=Object.create(null);t.sort((t,e)=>t.provider!==e.provider?t.provider.localeCompare(e.provider):t.prefix!==e.prefix?t.prefix.localeCompare(e.prefix):t.name.localeCompare(e.name));let i={provider:"",prefix:"",name:""};return t.forEach(t=>{if(i.name===t.name&&i.prefix===t.prefix&&i.provider===t.provider)return;i=t;const r=t.provider,o=t.prefix,s=t.name,c=n[r]||(n[r]=Object.create(null)),a=c[o]||(c[o]=y(r,o));let u;u=s in a.icons?e.loaded:""===o||a.missing.has(s)?e.missing:e.pending;const l={provider:r,prefix:o,name:s};u.push(l)}),e}(function(t,e=!0,n=!1){const i=[];return t.forEach(t=>{const r="string"==typeof t?l(t,e,n):t;r&&i.push(r)}),i}(t,!0,k()));if(!n.pending.length){let t=!0;return e&&setTimeout(()=>{t&&e(n.loaded,n.missing,n.pending,G)}),()=>{t=!1}}const i=Object.create(null),r=[];let o,s;return n.pending.forEach(t=>{const{provider:e,prefix:n}=t;if(n===s&&e===o)return;o=e,s=n,r.push(y(e,n));const c=i[e]||(i[e]=Object.create(null));c[n]||(c[n]=[])}),n.pending.forEach(t=>{const{provider:e,prefix:n,name:r}=t,o=y(e,n),s=o.pendingIcons||(o.pendingIcons=new Set);s.has(r)||(s.add(r),i[e][n].push(r))}),r.forEach(t=>{const e=i[t.provider][t.prefix];e.length&&X(t,e)}),e?function(t,e,n){const i=T++,r=E.bind(null,n,i);if(!e.pending.length)return r;const o={id:i,icons:e,callback:t,abort:r};return n.forEach(t=>{(t.loaderCallbacks||(t.loaderCallbacks=[])).push(o)}),r}(e,n,r):G},Z=t=>new Promise((e,i)=>{const r="string"==typeof t?l(t,!0):t;r?Y([r||t],o=>{if(o.length&&r){const t=A(r);if(t)return void e({...n,...t})}i(t)}):i(t)});function tt(t){try{const e="string"==typeof t?JSON.parse(t):t;if("string"==typeof e.body)return{...e}}catch(t){}}let et=!1;try{et=0===navigator.vendor.indexOf("Apple")}catch(t){}const nt=/(-?[0-9.]*[0-9]+[0-9.]*)/g,it=/^-?[0-9.]*[0-9]+[0-9.]*$/g;function rt(t,e,n){if(1===e)return t;if(n=n||100,"number"==typeof t)return Math.ceil(t*e*n)/n;if("string"!=typeof t)return t;const i=t.split(nt);if(null===i||!i.length)return t;const r=[];let o=i.shift(),s=it.test(o);for(;;){if(s){const t=parseFloat(o);isNaN(t)?r.push(o):r.push(Math.ceil(t*e*n)/n)}else r.push(o);if(o=i.shift(),void 0===o)return r.join("");s=!s}}function ot(t,e){const i={...n,...t},r={...o,...e},s={left:i.left,top:i.top,width:i.width,height:i.height};let c=i.body;[i,r].forEach(t=>{const e=[],n=t.hFlip,i=t.vFlip;let r,o=t.rotate;switch(n?i?o+=2:(e.push("translate("+(s.width+s.left).toString()+" "+(0-s.top).toString()+")"),e.push("scale(-1 1)"),s.top=s.left=0):i&&(e.push("translate("+(0-s.left).toString()+" "+(s.height+s.top).toString()+")"),e.push("scale(1 -1)"),s.top=s.left=0),o<0&&(o-=4*Math.floor(o/4)),o%=4,o){case 1:r=s.height/2+s.top,e.unshift("rotate(90 "+r.toString()+" "+r.toString()+")");break;case 2:e.unshift("rotate(180 "+(s.width/2+s.left).toString()+" "+(s.height/2+s.top).toString()+")");break;case 3:r=s.width/2+s.left,e.unshift("rotate(-90 "+r.toString()+" "+r.toString()+")")}o%2==1&&(s.left!==s.top&&(r=s.left,s.left=s.top,s.top=r),s.width!==s.height&&(r=s.width,s.width=s.height,s.height=r)),e.length&&(c=function(t,e,n){const i=function(t,e="defs"){let n="";const i=t.indexOf("<"+e);for(;i>=0;){const r=t.indexOf(">",i),o=t.indexOf("",o);if(-1===s)break;n+=t.slice(r+1,o).trim(),t=t.slice(0,i).trim()+t.slice(s+1)}return{defs:n,content:t}}(t);return r=i.defs,o=e+i.content+n,r?""+r+""+o:o;var r,o}(c,'',""))});const a=r.width,u=r.height,l=s.width,f=s.height;let d,h;null===a?(h=null===u?"1em":"auto"===u?f:u,d=rt(h,l/f)):(d="auto"===a?l:a,h=null===u?rt(d,f/l):"auto"===u?f:u);const p={},g=(t,e)=>{(t=>"unset"===t||"undefined"===t||"none"===t)(e)||(p[t]=e.toString())};g("width",d),g("height",h);const b=[s.left,s.top,l,f];return p.viewBox=b.join(" "),{attributes:p,viewBox:b,body:c}}function st(t,e){let n=-1===t.indexOf("xlink:")?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(const t in e)n+=" "+t+'="'+e[t]+'"';return'"+t+""}function ct(t){return'url("'+function(t){return"data:image/svg+xml,"+function(t){return t.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(//g,"%3E").replace(/\s+/g," ")}(t)}(t)+'")'}let at=(()=>{let t;try{if(t=fetch,"function"==typeof t)return t}catch(t){}})();function ut(t){at=t}function lt(){return at}const ft={prepare:(t,e,n)=>{const i=[],r=function(t,e){const n=Q(t);if(!n)return 0;let i;if(n.maxURL){let t=0;n.resources.forEach(e=>{const n=e;t=Math.max(t,n.length)});const r=e+".json?icons=";i=n.maxURL-t-n.path.length-r.length}else i=0;return i}(t,e),o="icons";let s={type:o,provider:t,prefix:e,icons:[]},c=0;return n.forEach((n,a)=>{c+=n.length+1,c>=r&&a>0&&(i.push(s),s={type:o,provider:t,prefix:e,icons:[]},c=n.length),s.icons.push(n)}),i.push(s),i},send:(t,e,n)=>{if(!at)return void n("abort",424);let i=function(t){if("string"==typeof t){const e=Q(t);if(e)return e.path}return"/"}(e.provider);switch(e.type){case"icons":{const t=e.prefix,n=e.icons.join(",");i+=t+".json?"+new URLSearchParams({icons:n}).toString();break}case"custom":{const t=e.uri;i+="/"===t.slice(0,1)?t.slice(1):t;break}default:return void n("abort",400)}let r=503;at(t+i).then(t=>{const e=t.status;if(200===e)return r=501,t.json();setTimeout(()=>{n(function(t){return 404===t}(e)?"abort":"next",e)})}).then(t=>{"object"==typeof t&&null!==t?setTimeout(()=>{n("success",t)}):setTimeout(()=>{404===t?n("abort",t):n("next",r)})}).catch(()=>{n("next",r)})}};function dt(t,e,n){y(n||"",e).loadIcons=t}function ht(t,e,n){y(n||"",e).loadIcon=t}const pt="data-style";let gt="";function bt(t){gt=t}function vt(t,e){let n=Array.from(t.childNodes).find(t=>t.hasAttribute&&t.hasAttribute(pt));n||(n=document.createElement("style"),n.setAttribute(pt,pt),t.appendChild(n)),n.textContent=":host{display:inline-block;vertical-align:"+(e?"-0.125em":"0")+"}span,svg{display:block;margin:auto}"+gt}const mt={"background-color":"currentColor"},yt={"background-color":"transparent"},xt={image:"var(--svg)",repeat:"no-repeat",size:"100% 100%"},_t={"-webkit-mask":mt,mask:mt,background:yt};for(const t in _t){const e=_t[t];for(const n in xt)e[t+"-"+n]=xt[n]}function wt(t){return t?t+(t.match(/^[-0-9.]+$/)?"px":""):"inherit"}let kt;function At(t){return void 0===kt&&function(){try{kt=window.trustedTypes.createPolicy("iconify",{createHTML:t=>t})}catch(t){kt=null}}(),kt?kt.createHTML(t):t}function jt(t){return Array.from(t.childNodes).find(t=>{const e=t.tagName&&t.tagName.toUpperCase();return"SPAN"===e||"SVG"===e})}function Ot(t,e){const i=e.icon.data,r=e.customisations,o=ot(i,r);r.preserveAspectRatio&&(o.attributes.preserveAspectRatio=r.preserveAspectRatio);const s=e.renderedMode;let c;if("svg"===s)c=function(t){const e=document.createElement("span"),n=t.attributes;let i="";n.width||(i="width: inherit;"),n.height||(i+="height: inherit;"),i&&(n.style=i);const r=st(t.body,n);return e.innerHTML=At(r),e.firstChild}(o);else c=function(t,e,n){const i=document.createElement("span");let r=t.body;-1!==r.indexOf("{this._check()}))}_check(){if(!this._checkQueued)return;this._checkQueued=!1;const t=this._state,e=this.getAttribute("icon");if(e!==t.icon.value)return void this._iconChanged(e);if(!t.rendered||!this._visible)return;const n=this.getAttribute("mode"),i=a(this);t.attrMode===n&&!function(t,e){for(const n in c)if(t[n]!==e[n])return!0;return!1}(t.customisations,i)&&jt(this._shadowRoot)||this._renderIcon(t.icon,i,n)}_iconChanged(t){const e=function(t,e){if("object"==typeof t)return{data:tt(t),value:t};if("string"!=typeof t)return{value:t};if(t.includes("{")){const e=tt(t);if(e)return{data:e,value:t}}const n=l(t,!0,!0);if(!n)return{value:t};const i=A(n);if(void 0!==i||!n.prefix)return{value:t,name:n,data:i};const r=Y([n],()=>e(t,n,A(n)));return{value:t,name:n,loading:r}}(t,(t,e,n)=>{const i=this._state;if(i.rendered||this.getAttribute("icon")!==t)return;const r={value:t,name:e,data:n};r.data?this._gotIconData(r):i.icon=r});e.data?this._gotIconData(e):this._state=Ct(e,this._state.inline,this._state)}_forceRender(){if(!this._visible){const t=jt(this._shadowRoot);return void(t&&this._shadowRoot.removeChild(t))}this._queueCheck()}_gotIconData(t){this._checkQueued=!1,this._renderIcon(t,a(this),this.getAttribute("mode"))}_renderIcon(t,e,n){const i=function(t,e){switch(e){case"svg":case"bg":case"mask":return e}return"style"===e||!et&&-1!==t.indexOf("{const e=t.some(t=>t.isIntersecting);e!==this._visible&&(this._visible=e,this._forceRender())}),this._observer.observe(this)}catch(t){if(this._observer){try{this._observer.disconnect()}catch(t){}this._observer=null}}}stopObserver(){this._observer&&(this._observer.disconnect(),this._observer=null,this._visible=!0,this._connected&&this._forceRender())}};r.forEach(t=>{t in o.prototype||Object.defineProperty(o.prototype,t,{get:function(){return this.getAttribute(t)},set:function(e){null!==e?this.setAttribute(t,e):this.removeAttribute(t)}})});const s=function(){let t;R("",ft),k(!0);try{t=window}catch(t){}if(t){if(void 0!==t.IconifyPreload){const e=t.IconifyPreload,n="Invalid IconifyPreload syntax.";"object"==typeof e&&null!==e&&(e instanceof Array?e:[e]).forEach(t=>{try{("object"!=typeof t||null===t||t instanceof Array||"object"!=typeof t.icons||"string"!=typeof t.prefix||!O(t))&&console.error(n)}catch(t){console.error(n)}})}if(void 0!==t.IconifyProviders){const e=t.IconifyProviders;if("object"==typeof e&&null!==e)for(const t in e){const n="IconifyProviders["+t+"] is invalid.";try{const i=e[t];if("object"!=typeof i||!i||void 0===i.resources)continue;z(t,i)||console.error(n)}catch(t){console.error(n)}}}}return{iconLoaded:C,getIcon:I,listIcons:_,addIcon:j,addCollection:O,calculateSize:rt,buildIcon:ot,iconToHTML:st,svgToURL:ct,loadIcons:Y,loadIcon:Z,addAPIProvider:z,setCustomIconLoader:ht,setCustomIconsLoader:dt,appendCustomStyle:bt,_api:{getAPIConfig:Q,setAPIModule:R,sendAPIQuery:B,setFetch:ut,getFetch:lt,listAPIProviders:q}}}();for(const t in s)o[t]=o.prototype[t]=s[t];e.define(t,o)}()}(); diff --git a/_extensions/mcanouil/iconify/iconify.lua b/_extensions/mcanouil/iconify/iconify.lua new file mode 100644 index 0000000..2051402 --- /dev/null +++ b/_extensions/mcanouil/iconify/iconify.lua @@ -0,0 +1,265 @@ +--- @module iconify +--- @license MIT +--- @copyright 2026 MickaΓ«l Canouil +--- @author MickaΓ«l Canouil + +--- Extension name constant +local EXTENSION_NAME = "iconify" + +--- Load utils module +local utils = require(quarto.utils.resolve_path("_modules/utils.lua"):gsub("%.lua$", "")) + +--- Flag to track if deprecation warning has been shown +--- @type boolean +local deprecation_warning_shown = false + +--- Ensure Iconify HTML dependencies are included. +--- @return nil +local function ensure_html_deps() + quarto.doc.add_html_dependency({ + name = 'iconify', + version = '3.0.0', + scripts = { 'iconify-icon.min.js' } + }) +end + +--- Check for deprecated top-level iconify configuration and emit warning. +--- @param meta table Document metadata table +--- @param key string The configuration key being accessed +--- @return string|nil The value from deprecated config, or nil if not found +local function check_deprecated_config(meta, key) + local value + value, deprecation_warning_shown = utils.check_deprecated_config(meta, 'iconify', key, deprecation_warning_shown) + return value +end + +--- Validate and convert size keyword to CSS font-size. +--- @param size string|nil +--- @return string +local function is_valid_size(size) + if utils.is_empty(size) then + return '' + end + --- @type table + local size_table = { + ['tiny'] = '0.5em', + ['scriptsize'] = '0.7em', + ['footnotesize'] = '0.8em', + ['small'] = '0.9em', + ['normalsize'] = '1em', + ['large'] = '1.2em', + ['Large'] = '1.5em', + ['LARGE'] = '1.75em', + ['huge'] = '2em', + ['Huge'] = '2.5em', + ['1x'] = '1em', + ['2x'] = '2em', + ['3x'] = '3em', + ['4x'] = '4em', + ['5x'] = '5em', + ['6x'] = '6em', + ['7x'] = '7em', + ['8x'] = '8em', + ['9x'] = '9em', + ['10x'] = '10em', + ['2xs'] = '0.625em', + ['xs'] = '0.75em', + ['sm'] = '0.875em', + ['lg'] = '1.25em', + ['xl'] = '1.5em', + ['2xl'] = '2em' + } + for key, value in pairs(size_table) do + if key == size then + return 'font-size: ' .. value .. ';' + end + end + return 'font-size: ' .. size .. ';' +end + +--- Get iconify option from arguments or metadata. +--- @param x string The option name to retrieve +--- @param arg table Arguments table containing options +--- @param meta table Document metadata table +--- @return string The option value as a string +local function get_iconify_options(x, arg, meta) + --- @type string + local arg_value = utils.stringify(arg[x]) + + -- Return argument value if provided + if not utils.is_empty(arg_value) then + return arg_value + end + + -- Check new nested structure: extensions.iconify.x + local meta_value = utils.get_metadata_value(meta, 'iconify', x) + if not utils.is_empty(meta_value) then + return meta_value + end + + -- Check deprecated top-level structure: iconify.x (with warning) + local deprecated_value = check_deprecated_config(meta, x) + if deprecated_value then + return deprecated_value + end + + return arg_value +end + +--- Render an Iconify icon as a Pandoc RawInline for HTML output. +--- @param args table Icon arguments (icon set and name) +--- @param kwargs table Key-value options for the icon +--- @param meta table Document metadata +--- @return any Pandoc RawInline for HTML or Pandoc Null for other formats +local function iconify(args, kwargs, meta) + -- detect html (excluding epub which won't handle fa) + if quarto.doc.is_format('html:js') then + ensure_html_deps() + --- @type string + local icon = utils.stringify(args[1]) + --- @type string + local set = 'octicon' + + -- Check new nested structure for default set + local meta_set = utils.get_metadata_value(meta, 'iconify', 'set') + if not utils.is_empty(meta_set) then + set = meta_set + else + -- Check deprecated top-level structure for default set (with warning) + local deprecated_set = check_deprecated_config(meta, 'set') + if deprecated_set then + set = deprecated_set + end + end + + if #args > 1 and string.find(utils.stringify(args[2]), ':') then + utils.log_warning( + EXTENSION_NAME, + 'Use "set:icon" or "set icon" syntax, not both! ' .. + 'Using "set:icon" syntax and discarding first argument!' + ) + icon = utils.stringify(args[2]) + end + + if string.find(icon, ':') then + set = string.sub(icon, 1, string.find(icon, ':') - 1) + icon = string.sub(icon, string.find(icon, ':') + 1) + elseif #args > 1 then + set = icon + icon = utils.stringify(args[2]) + end + + --- @type string + local attributes = ' icon="' .. set .. ':' .. icon .. '"' + --- @type string + local default_label = 'Icon ' .. icon .. ' from ' .. set .. ' Iconify.design set.' + + --- @type string + local size = is_valid_size(get_iconify_options('size', kwargs, meta)) + --- @type string + local style = get_iconify_options('style', kwargs, meta) + + if utils.is_empty(style) and not utils.is_empty(size) then + attributes = attributes .. ' style="' .. size .. '"' + elseif not utils.is_empty(style) and not utils.is_empty(size) then + attributes = attributes .. ' style="' .. style .. ';' .. size .. '"' + elseif not utils.is_empty(style) then + attributes = attributes .. ' style="' .. style .. '"' + end + + --- @type string + local aria_label = utils.stringify(kwargs['label']) + if utils.is_empty(aria_label) then + aria_label = ' aria-label="' .. default_label .. '"' + else + aria_label = ' aria-label="' .. aria_label .. '"' + end + + --- @type string + local title = utils.stringify(kwargs['title']) + if utils.is_empty(title) then + title = ' title="' .. default_label .. '"' + else + title = ' title="' .. title .. '"' + end + + attributes = attributes .. aria_label .. title + + --- @type string + local width = get_iconify_options('width', kwargs, meta) + if not utils.is_empty(width) and utils.is_empty(size) then + attributes = attributes .. ' width="' .. width .. '"' + end + --- @type string + local height = get_iconify_options('height', kwargs, meta) + if not utils.is_empty(height) and utils.is_empty(size) then + attributes = attributes .. ' height="' .. height .. '"' + end + --- @type string + local flip = get_iconify_options('flip', kwargs, meta) + if not utils.is_empty(flip) then + attributes = attributes .. ' flip="' .. flip .. '"' + end + --- @type string + local rotate = get_iconify_options('rotate', kwargs, meta) + if not utils.is_empty(rotate) then + attributes = attributes .. ' rotate="' .. rotate .. '"' + end + + --- @type string + local inline = get_iconify_options('inline', kwargs, meta) + if utils.is_empty(inline) or inline ~= 'false' then + attributes = ' inline ' .. attributes + end + + --- @type string + local mode = get_iconify_options('mode', kwargs, meta) + --- @type table + local valid_modes = { svg = true, style = true, bg = true, mask = true } + if not utils.is_empty(mode) and valid_modes[mode] then + attributes = attributes .. ' mode="' .. mode .. '"' + end + + return pandoc.RawInline( + 'html', + '' + ) + else + return pandoc.Null() + end +end + +--- Render Quarto icon using the iconify function with preset styling. +--- @param args table Icon arguments (ignored as we're using a preset icon) +--- @param kwargs table|nil Key-value options that might override default styling +--- @param meta table Document metadata +--- @return any Pandoc RawInline for HTML or Pandoc Null for other formats +local function iconify_quarto(args, kwargs, meta) + --- @type table + local quarto_args = { 'simple-icons:quarto' } + --- @type table + local quarto_kwargs = kwargs or {} + quarto_kwargs['label'] = 'Quarto icon' + quarto_kwargs['title'] = 'Quarto icon' + --- @type string + local quarto_colour = 'color:#74aadb;' + + if not utils.is_empty(quarto_kwargs['style']) then + --- @type string + local style = utils.stringify(quarto_kwargs['style']) + if string.match(style, 'color:[^;]+;') then + quarto_kwargs['style'] = string.gsub(style, 'color:[^;]+;', quarto_colour) + else + quarto_kwargs['style'] = quarto_colour .. style + end + else + quarto_kwargs['style'] = quarto_colour + end + return iconify(quarto_args, quarto_kwargs, meta) +end + +--- @type table +return { + ['iconify'] = iconify, + ['quarto'] = iconify_quarto +} From 423e99526e2e08c698944260c185a9192e8360bd Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:08:47 -0600 Subject: [PATCH 05/20] add contributor guide --- contributing/colors-and-fonts.qmd | 237 ++++++++++++++++ contributing/documenting-code.qmd | 41 +++ contributing/index.qmd | 37 +++ contributing/scaffolding.qmd | 451 ++++++++++++++++++++++++++++++ contributing/testing-code.qmd | 4 + contributing/user-guide-pages.qmd | 50 ++++ contributing/writing-code.qmd | 16 ++ 7 files changed, 836 insertions(+) create mode 100644 contributing/colors-and-fonts.qmd create mode 100644 contributing/documenting-code.qmd create mode 100644 contributing/index.qmd create mode 100644 contributing/scaffolding.qmd create mode 100644 contributing/testing-code.qmd create mode 100644 contributing/user-guide-pages.qmd create mode 100644 contributing/writing-code.qmd diff --git a/contributing/colors-and-fonts.qmd b/contributing/colors-and-fonts.qmd new file mode 100644 index 0000000..27536e6 --- /dev/null +++ b/contributing/colors-and-fonts.qmd @@ -0,0 +1,237 @@ +--- +title: "Colors and Fonts" +engine: knitr +--- + +```{=html} + +``` + +Our colors and themes are inspired by the painfully 90's color choices of the R +Foundation, particularly [its logo](https://www.r-project.org/logo/), and the +Rust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). + +## Colors + +We have defined several utility classes to make using consistent colors easier. +These can be used to define background and text colors. They are also used to +define semantic colors, which are applied automatically. + +### Background colors + +:::::: {.d-flex} +::: {.bg-dark-blue-4 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-dark-blue-4 +::: +::: {.bg-dark-blue-3 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-dark-blue-3 +::: +::: {.bg-dark-blue-2 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-dark-blue-2 +::: +::: {.bg-dark-blue-1 .color-swatch style="width: 160px; height: 40px;"} +.bg-dark-blue-1 +::: +:::::: + +:::::: {.d-flex .mt-3} +::: {.bg-orange-4 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-orange-4 +::: +::: {.bg-orange-3 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-orange-3 +::: +::: {.bg-orange-2 .color-swatch style="width: 160px; height: 40px;"} +.bg-orange-2 +::: +::: {.bg-orange-1 .color-swatch style="width: 160px; height: 40px;"} +.bg-orange-1 +::: +:::::: + +:::::: {.d-flex .mt-3} +::: {.bg-blue-4 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-blue-4 +::: +::: {.bg-blue-3 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-blue-3 +::: +::: {.bg-blue-2 .color-swatch style="width: 160px; height: 40px;"} +.bg-blue-2 +::: +::: {.bg-blue-1 .color-swatch style="width: 160px; height: 40px;"} +.bg-blue-1 +::: +:::::: + +:::::: {.d-flex .mt-3} +::: {.bg-silver-4 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-silver-4 +::: +::: {.bg-silver-3 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-silver-3 +::: +::: {.bg-silver-2 .color-swatch style="width: 160px; height: 40px;"} +.bg-silver-2 +::: +::: {.bg-silver-1 .color-swatch style="width: 160px; height: 40px;"} +.bg-silver-1 +::: +:::::: + +:::::: {.d-flex .mt-3} +::: {.bg-teal-4 .color-swatch style="width: 160px; height: 40px;"} +.bg-teal-4 +::: +::: {.bg-teal-3 .color-swatch style="width: 160px; height: 40px;"} +.bg-teal-3 +::: +::: {.bg-teal-2 .color-swatch style="width: 160px; height: 40px;"} +.bg-teal-2 +::: +::: {.bg-teal-1 .color-swatch style="width: 160px; height: 40px;"} +.bg-teal-1 +::: +:::::: + +:::::: {.d-flex .mt-3} +::: {.bg-r-blue-2 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-r-blue-2 +::: +::: {.bg-r-blue-1 .color-swatch .text-white style="width: 160px; height: 40px;"} +.bg-r-blue-1 +::: +:::::: + +### Text colors + +[.text-dark-blue-4]{.text-dark-blue-4}, [.text-dark-blue-3]{.text-dark-blue-3}, [.text-dark-blue-2]{.text-dark-blue-2}, [.text-dark-blue-1]{.text-dark-blue-1} + +[.text-orange-4]{.text-orange-4}, [.text-orange-3]{.text-orange-3}, [.text-orange-2]{.text-orange-2}, [.text-orange-1]{.text-orange-1} + +[.text-blue-4]{.text-blue-4}, [.text-blue-3]{.text-blue-3}, [.text-blue-2]{.text-blue-2}, [.text-blue-1]{.text-blue-1} + +[.text-silver-4]{.text-silver-4}, [.text-silver-3]{.text-silver-3}, [.text-silver-2]{.text-silver-2}, [.text-silver-1]{.text-silver-1} + +[.text-teal-4]{.text-teal-4}, [.text-teal-3]{.text-teal-3}, [.text-teal-2]{.text-teal-2}, [.text-teal-1]{.text-teal-1} + +[.text-r-blue-2]{.text-r-blue-2}, [.text-r-blue-1]{.text-r-blue-1} + +### Semantic colors + +[Primary](#){.btn .btn-primary .me-1} +[Secondary](#){.btn .btn-secondary .me-1} +[Success](#){.btn .btn-success .me-1} +[Danger](#){.btn .btn-danger .me-1} +[Warning](#){.btn .btn-warning .me-1} +[Info](#){.btn .btn-info .me-1} +[Light](#){.btn .btn-light .me-1} +[Dark](#){.btn .btn-dark .me-1} + +These will also apply to [Quarto callouts](https://quarto.org/docs/authoring/callouts.html). + +::: callout-note +Note that there are five types of callouts, including: `note`, `tip`, `warning`, +`caution`, and `important`. +::: + +::: callout-warning +Callouts provide a simple way to attract attention, for example, to this warning. +::: + +::: callout-important +The callout heading is provided by the callout type, with the expected heading +(i.e., Note, Warning, Important, Tip, or Caution). +::: + +::: callout-tip +## Tip With Title + +This is an example of a callout with a title. Providing a callout heading is +optional. +::: + +::: {.callout-caution collapse="true"} +## Caution: Expand To Learn About Collapse + +This is an example of a 'collapsed' caution callout that can be expanded by the +user. You can use `collapse="true"` to collapse it by default or `collapse="false"` +to make a collapsible callout that is expanded by default. +::: + +And they have utility classes for text colors: + +[.text-primary]{.text-primary}, [.text-primary-emphasis]{.text-primary-emphasis}, +[.text-secondary]{.text-secondary}, [.text-secondary-emphasis]{.text-secondary-emphasis}, +[.text-success]{.text-success}, [.text-success-emphasis]{.text-success-emphasis}, +[.text-danger]{.text-danger}, [.text-danger-emphasis]{.text-danger-emphasis}, +[.text-warning]{.text-warning}, [.text-warning-emphasis]{.text-warning-emphasis}, +[.text-info]{.text-info}, [.text-info-emphasis]{.text-info-emphasis}, +[.text-light]{.text-light}, [.text-light-emphasis]{.text-light-emphasis}, +[.text-dark]{.text-dark}, [.text-dark-emphasis]{.text-dark-emphasis}, +[.text-body]{.text-body}, [.text-body-emphasis]{.text-body-emphasis}, +[.text-body-secondary]{.text-body-secondary}, [.text-body-tertiary]{.text-body-tertiary} + +## Fonts + +We follow the Rust Foundation in using Noto Sans for body and smaller texts and +Noto Serif for headers. We have also implemented utility classes for these, +though they should not be required. + +:::::: {.d-flex .gap-3} +::: {.bg-dark-blue-3 .text-white .font-serif .p-3 .flex-fill} +[Noto Serif]{.fs-3} +.font-serif +ABCDEFGHIJKLMNOPQRSTUVWXYZ +abcdefghijklmnopqrstuvwxyz +0123456789 +::: + +::: {.bg-silver-4 .font-sans .p-3 .flex-fill .text-auto-dark} +[Noto Sans]{.fs-3} +.font-sans +ABCDEFGHIJKLMNOPQRSTUVWXYZ +abcdefghijklmnopqrstuvwxyz +0123456789 +::: +:::::: + +## Light and Dark + +The `.bg-*` and `.text-*` utility classes automatically adapt to light and dark +mode by flipping the tint scale β€” so `.text-dark-blue-4` in light mode becomes +`.text-dark-blue-1` in dark mode, and `.text-dark-blue-1` in light mode becomes +`.text-dark-blue-4` in dark mode. This means you generally don't need to +think about dark mode when using these classes. If you need a color to stay +fixed regardless of the theme, however, simply append `-fixed` to the class +name: `.bg-orange-4-fixed` and `.text-orange-4-fixed` will always render the +original color. + +Here are some examples. To see how they work, toggle light and dark mode in the +browser: + +[.bg-orange-4]{.bg-orange-4} +[.bg-orange-4-fixed]{.bg-orange-4-fixed} + +Notice that the font changes from black to white in each example. This is +Bootstrap's color system adapting to the page background, which is meant to +ensure a certain contrast level for accessibility. Semantic colors (`.text-primary`, +`.btn-success`, callouts, etc.) are also handled by Bootstrap's color system and +do not need any special treatment. However, you will notice that Bootstrap's +color system does not recognize these utilities - currently, it does not even +recognize its own utilities! - so contrast can be lost when using them. To +ensure that accessibility is maintained, you may want to add `.text-auto-dark` +or `.text-auto-light` classes, too. These will ensure that white and black text +are correctly used with our utilities. + +[.bg-orange-1 with .text-auto-light]{.bg-orange-1 .text-auto-light} +[.bg-orange-4 with .text-auto-dark]{.bg-orange-4 .text-auto-dark} diff --git a/contributing/documenting-code.qmd b/contributing/documenting-code.qmd new file mode 100644 index 0000000..161b441 --- /dev/null +++ b/contributing/documenting-code.qmd @@ -0,0 +1,41 @@ +--- +title: "Documenting Code" +engine: knitr +--- + +Code in extendr and rextendr should be thoroughly documented using widely +accepted styling conventions in the R and Rust communities. + +## Writing style + +- Be concise! And stay on topic! The reader should not have to wade through + lengthy digressions to get to the point. +- Eschew obfuscation and avoid needlessly wordy phrases like 'eschew + obfuscation.' In other words, keep it simple. +- Second person ("you") is fine for instructions, but do not write about the + reader's intentions in the second person (avoid expressions like "you want to" + or "you need to"). + + +## Rust Documentation + +## R Documentation + +Functions in rextendr should be documented with [roxygen2](https://roxygen2.r-lib.org), +following the conventions recommended by the [tidyverse style guide](https://style.tidyverse.org/documentation.html). + +- Every exported function in rextendr must include the following roxygen + variables: + - `@title` + - `@description` β€” a concise description of what the function does. + - `@param` β€” one tag per parameter, specifying: + - the data type (e.g. `integer`, `character`, `logical`) + - whether it is a scalar or vector + - the default value, if one exists + - `@return` β€” what the function returns, including the R type. +- The `@details` and `@examples` sections are optional, but should in general be + included. +- Internal functions should include roxygen comments if their use in rextendr is + fairly complex, important, and not easy to discern. Simple helper functions do + not need to be documented. + diff --git a/contributing/index.qmd b/contributing/index.qmd new file mode 100644 index 0000000..179a701 --- /dev/null +++ b/contributing/index.qmd @@ -0,0 +1,37 @@ +--- +title: "Contributing" +page-layout: article +toc: true +toc-depth: 0 +date: today +date-format: long +--- + +The extendr project has three main areas where you can contribute: + +**extendr crates** β€” The core Rust library. This is where the `#[extendr]` +macro, R type bindings, and the bulk of the Rust API live. +Contributions here require Rust experience. The repository is at +[github.com/extendr/extendr](https://github.com/extendr/extendr). + +**rextendr package** β€” The R package that scaffolds new extendr projects +and generates R wrapper functions. Contributions here require R +package development experience. The repository is at +[github.com/extendr/rextendr](https://github.com/extendr/rextendr). + +**Documentation website** β€” The site you are reading now. Contributions +here range from fixing typos to writing new guides and tutorials. The +repository is at +[github.com/extendr/extendr.github.io](https://github.com/extendr/extendr.github.io). + +## Software requirements + +This guide assumes the following versions of necessary software: + +- R: {{< var version.r >}} +- Rust {{< var version.rust >}} +- extendr-api {{< var version.extendr >}} +- rextendr {{< var version.rextendr >}} + +Please see [Get Started](../get-started.qmd) if you have not already installed this +software. \ No newline at end of file diff --git a/contributing/scaffolding.qmd b/contributing/scaffolding.qmd new file mode 100644 index 0000000..366d1e6 --- /dev/null +++ b/contributing/scaffolding.qmd @@ -0,0 +1,451 @@ +--- +title: "Scaffolding" +--- + +This page describes how `rextendr::use_extendr()` generates its scaffolding, +intended for contributors who want to modify or add scaffolded files. For a +description of what each generated file does, see the +[Project Structure](../user-guide/package-structure.qmd) page in the user guide. +As noted there, the package directory should have the following structure: + +``` +. +β”œβ”€β”€ R +β”‚ └── extendr-wrappers.R +β”œβ”€β”€ cleanup +β”œβ”€β”€ cleanup.win +β”œβ”€β”€ configure +β”œβ”€β”€ configure.win +β”œβ”€β”€ src +β”‚ β”œβ”€β”€ .gitignore +β”‚ β”œβ”€β”€ Makevars.in +β”‚ β”œβ”€β”€ Makevars.win.in +β”‚ β”œβ”€β”€ entrypoint.c +β”‚ β”œβ”€β”€ -win.def +β”‚ └── rust +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ β”œβ”€β”€ document.rs +β”‚ └── lib.rs +└── tools + β”œβ”€β”€ config.R + └── msrv.R +``` + +## Template generation + +Nearly all files generated by `use_extendr()` come from templates stored in +`inst/templates/`. Each template is written to the user's package directory via +the internal function `use_rextendr_template()`: + +```{.r filename="R/use_extendr.R"} +use_rextendr_template <- function( + template, # filename in inst/templates/ + save_as, # destination path in the user's package + data = list(), # named list of variables to interpolate into the template + quiet = FALSE, + overwrite = NULL +) +``` + +When `usethis` is available and `overwrite = NULL`, this delegates to +`usethis::use_template()`, which handles interactive prompting if the file +already exists. Otherwise it reads the template file directly, performs +interpolation, and writes the result. + +### Variable interpolation + +By default, template variables use triple-brace syntax: `{{{variable_name}}}`. +Currently, interpolation is performed using `glue` with `.open = "{{{"` and +`.close = "}}}"`. Triple braces avoid conflicts with Rust's `{}` format syntax +and Makefile's `${}` variable syntax, both of which appear in some templates. + +The following variables are passed to templates, all derived from the user's +package name: + +| Variable | Description | Example | +|----------------------------|---------------------------------------------------|-------------| +| `{{{pkg_name}}}` | R package name, as-is | `hellorust` | +| `{{{mod_name}}}` | Package name sanitized to a valid Rust identifier | `hellorust` | +| `{{{lib_name}}}` | Rust library name | `hellorust` | +| `{{{cargo_toml_content}}}` | Full generated `Cargo.toml` content (see below) | β€” | + +If `NULL`, `mod_name` and `lib_name` are derived from `pkg_name`, which is +sanitized to ensure it is compliant with Rust naming conventions. They can also +be set independently via the `crate_name` and `lib_name` arguments to +`use_extendr()`. + +### The `inst/templates/` directory + +The templates directory currently includes all of the following: + +``` +inst/templates/ +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ Makevars.in +β”œβ”€β”€ Makevars.win.in +β”œβ”€β”€ _gitignore +β”œβ”€β”€ cleanup +β”œβ”€β”€ cleanup.win +β”œβ”€β”€ config.R +β”œβ”€β”€ configure +β”œβ”€β”€ configure.win +β”œβ”€β”€ document.rs +β”œβ”€β”€ entrypoint.c +β”œβ”€β”€ extendr-wrappers.R +β”œβ”€β”€ lib.rs +β”œβ”€β”€ msrv.R +β”œβ”€β”€ settings.json +└── win.def +``` + +Most templates are written verbatim or with simple string interpolation. The +exceptions are `Cargo.toml`, whose content is generated programmatically before +being passed to the template, and `Makevars.in` / `Makevars.win.in`, which use a +second layer of `@PLACEHOLDER@` substitution performed at package build time by +`tools/config.R`. + +## Rust source templates + +### `lib.rs` + +`lib.rs` is the entry point for the user's Rust library. The template provides +a minimal working example β€” a single `hello_world()` function β€” so the package +compiles and can be called from R immediately after scaffolding. + +```{.rust filename="inst/templates/lib.rs"} +use extendr_api::prelude::*; + +/// Return string `"Hello world!"` to R. +/// @export +#[extendr] +fn hello_world() -> &'static str { + "Hello world!" +} + +// Macro to generate exports. +// This ensures exported functions are registered with R. +// See corresponding C code in `entrypoint.c`. +extendr_module! { + mod {{{mod_name}}}; + fn hello_world; +} +``` + +The `extendr_module!` macro is the mechanism by which Rust functions, `impl` +blocks, and submodules are registered with R. The first entry must always be +`mod {{{mod_name}}}`, which declares the module name used internally by the +extendr runtime. Every additional entry β€” functions and `impl` blocks β€” must +be listed explicitly, or it will not be available in R. Users add their own +Rust functions to this file and register them in `extendr_module!`. + +### `document.rs` + +`document.rs` is a Rust binary that generates `R/extendr-wrappers.R`. It is +compiled alongside the library crate (registered as `[[bin]]` in `Cargo.toml`) +and run by `Makevars` via `cargo run --bin document` each time the package is +built. + +```{.rust filename="inst/templates/document.rs"} +// Generated by extendr: Do not edit by hand +fn main() -> Result<(), Box> { + let wrapper_path = "../R/extendr-wrappers.R"; + let header = "\ + # Generated by extendr: Do not edit by hand\n\ + # nolint start\n\ + \n\ + #' @usage NULL\n\ + #' @useDynLib {{{pkg_name}}}, .registration = TRUE\n\ + NULL\n\ + \n\ + "; + let footer = "# nolint end\n"; + let wrappers = {{{lib_name}}}::get_{{{mod_name}}}_metadata() + .make_r_wrappers(true, "{{{pkg_name}}}") + .map_err(|e| format!("failed to generate wrappers: {e}"))?; + std::fs::write(wrapper_path, format!("{header}{wrappers}{footer}")) + .map_err(|e| format!("failed to write {wrapper_path}: {e}"))?; + Ok(()) +} +``` + +The binary calls `get_{{{mod_name}}}_metadata()`, a function generated by the +`#[extendr]` macro on each exported item, which collects metadata about all +exported functions and their signatures. `make_r_wrappers()` then uses that +metadata to produce the corresponding R wrapper code. The result is written +directly to `R/extendr-wrappers.R`, relative to the `src/rust/` directory +where the binary runs. + +### `Cargo.toml` + +Unlike other templates, the content of `Cargo.toml` is constructed in R via +`to_toml()` before being passed to the template as the single variable +`{{{cargo_toml_content}}}`. This allows the crate name, lib name, edition, and +`extendr-api` version to be set dynamically based on the user's package and +rextendr's internal defaults. The resulting `Cargo.toml` for a package called +`hellorust` looks like this: + +```{.toml filename="src/rust/Cargo.toml"} +[package] +name = 'hellorust' +publish = false +version = '0.1.0' +edition = '2021' +rust-version = '1.65' + +[lib] +crate-type = ['rlib', 'staticlib'] +name = 'hellorust' + +[[bin]] +name = 'document' +path = 'document.rs' + +[dependencies] +extendr-api = '*' + +[profile.release] +lto = true +codegen-units = 1 +``` + +The crate is built as both an `rlib` and a `staticlib`. The `staticlib` is +linked into the R package's shared library. The `rlib` is needed by the +`document` binary, which must link against the library crate to introspect its +exported function metadata. The release profile enables LTO and single-codegen-unit +compilation to produce smaller, faster binaries β€” important for CRAN packages. + +## Build file templates + +### `entrypoint.c` + +R requires that compiled packages register their routines via a C-level +initialization function named `R_init_`. extendr generates its own +version of this function β€” `R_init_{{{mod_name}}}_extendr` β€” inside the Rust +library. The C file bridges the two: it declares the Rust-generated function +and calls it from the R-facing `R_init_{{{mod_name}}}`. + +```{.c filename="inst/templates/entrypoint.c"} +// We need to forward routine registration from C to Rust +// to avoid the linker removing the static library. + +void R_init_{{{mod_name}}}_extendr(void *dll); + +void R_init_{{{mod_name}}}(void *dll) { + R_init_{{{mod_name}}}_extendr(dll); +} +``` + +Without this forwarding, the linker may strip the static Rust library entirely +when building the R package shared object, since nothing in the C compilation +unit directly references it. + +### `Makevars.in` and `Makevars.win.in` + +These are the Makefile templates that drive the Rust compilation step. They are +not used directly by R β€” `tools/config.R` reads the appropriate template, +substitutes the `@PLACEHOLDER@` variables, and writes the final `src/Makevars` +or `src/Makevars.win` at package build time. + +```{.makefile filename="inst/templates/Makevars.in"} +TARGET_DIR = ./rust/target +LIBDIR = $(TARGET_DIR)/@LIBDIR@ +STATLIB = $(LIBDIR)/lib{{{lib_name}}}.a +PKG_LIBS = -L$(LIBDIR) -l{{{lib_name}}} + +all: $(SHLIB) rust_clean + +.PHONY: $(STATLIB) + +$(SHLIB): $(STATLIB) + +CARGOTMP = $(CURDIR)/.cargo +VENDOR_DIR = $(CURDIR)/vendor + +$(STATLIB): + + if [ -d ./vendor ]; then \ + echo "=== Using offline vendor directory ==="; \ + mkdir -p $(CARGOTMP) && \ + cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ + elif [ -f ./rust/vendor.tar.xz ]; then \ + echo "=== Using offline vendor tarball ==="; \ + tar xf rust/vendor.tar.xz && \ + mkdir -p $(CARGOTMP) && \ + cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ + fi + + export CARGO_HOME=$(CARGOTMP) && \ + export PATH="$(PATH):$(HOME)/.cargo/bin" && \ + @PANIC_EXPORTS@RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --lib @PROFILE@ --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ + + export CARGO_HOME=$(CARGOTMP) && \ + export PATH="$(PATH):$(HOME)/.cargo/bin" && \ + cargo run @CRAN_FLAGS@ --bin document --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ + + # Always clean up CARGOTMP + rm -Rf $(CARGOTMP); + +rust_clean: $(SHLIB) + rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ + +clean: + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) $(VENDOR_DIR) +``` + +The build performs two `cargo` invocations: one to compile the static library +(`--lib`), and one to run the `document` binary that regenerates +`R/extendr-wrappers.R`. If a `vendor/` directory or `rust/vendor.tar.xz` +tarball is present, Cargo is configured to use it for offline compilation β€” +this is the mechanism used for CRAN submissions. + +The `@PLACEHOLDER@` variables are substituted by `tools/config.R`: + +| Placeholder | Description | +|-------------------|--------------------------------------------------------------------------------------------| +| `@LIBDIR@` | Path suffix to the compiled library, e.g. `release` or `wasm32-unknown-emscripten/release` | +| `@PROFILE@` | `--release` for release builds, empty for debug | +| `@CRAN_FLAGS@` | `-j 2 --offline` when building for CRAN with vendored deps, otherwise empty | +| `@TARGET@` | `--target=wasm32-unknown-emscripten` for WebR builds, otherwise empty | +| `@PANIC_EXPORTS@` | Sets `CARGO_PROFILE_*_PANIC=abort` env vars for WASM builds | +| `@CLEAN_TARGET@` | `$(TARGET_DIR)` on release builds, empty on debug builds | + +`Makevars.win.in` follows the same structure but targets the +`x86_64-pc-windows-gnu` toolchain and links additional Windows system +libraries (`ws2_32`, `advapi32`, `userenv`, `bcrypt`, `ntdll`). + +### `win.def` + +On Windows, the DLL export list must be specified explicitly. This file +declares the package's initialization routine as an export so that R can locate +it when loading the package. + +```{filename="inst/templates/win.def"} +EXPORTS +R_init_{{{mod_name}}} +``` + +### `_gitignore` + +Written to `src/.gitignore`. Excludes compiled artifacts, Cargo's working +directories, the vendor directory, and the generated `Makevars` files (which +are produced fresh at each build and should not be committed). + +```{filename="inst/templates/_gitignore"} +*.o +*.so +*.dll +target +.cargo +rust/vendor +Makevars +Makevars.win +``` + +The template is named `_gitignore` rather than `.gitignore` to prevent it from +being treated as a gitignore file within the rextendr repository itself. + +## Configuration scripts + +### `configure` and `configure.win` + +R runs `configure` (on Unix) or `configure.win` (on Windows) before compiling +a package. These scripts simply invoke `tools/config.R` via `Rscript`, which +does the real work of generating the final `Makevars` file. + +```{.bash filename="inst/templates/configure"} +#!/usr/bin/env sh +: "${R_HOME=`R RHOME`}" +"${R_HOME}/bin/Rscript" tools/config.R +``` + +```{.bash filename="inst/templates/configure.win"} +#!/usr/bin/env sh +"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/config.R +``` + +On Unix, `configure` must be executable. `use_extendr()` calls +`Sys.chmod("configure", "0755")` automatically after writing it. + +### `tools/msrv.R` + +This script reads the `SystemRequirements` field from `DESCRIPTION` to +determine the package's minimum supported Rust version (MSRV), then checks +that both `rustc` and `cargo` are installed and meet that requirement. It is +sourced by `tools/config.R` at the start of every build. + +The MSRV check works as follows: + +1. Confirms `SystemRequirements` contains both `cargo` and `rustc`. +2. Extracts the version string from the `rustc` entry, e.g. `rustc >= 1.65`. +3. Runs `rustc --version` and `cargo --version` on the system PATH (with + `~/.cargo/bin` appended). +4. Compares the installed `rustc` version against the MSRV using + `utils::compareVersion()`. Stops with an informative error if the installed + version is too old. +5. Prints the detected `cargo` and `rustc` versions to the build log β€” a + requirement for CRAN submissions. + +### `tools/config.R` + +`config.R` is the main build configuration script. It sources `tools/msrv.R`, +determines the correct build flags for the current context, and writes the +final `Makevars` file by substituting the `@PLACEHOLDER@` variables in the +appropriate `.in` template. The build context is determined by three environment +variables: + +| Variable | Effect | +|----------------------|------------------------------------------------------------------| +| `DEBUG` | Builds with `--debug` instead of `--release`; implies `NOT_CRAN` | +| `NOT_CRAN` | Disables CRAN flags (offline mode, job limits) | +| `R.version$platform` | If `wasm32-unknown-emscripten`, enables WebR-specific flags | + +When neither `DEBUG` nor `NOT_CRAN` is set and a `vendor.tar.xz` tarball is +present, `config.R` sets `@CRAN_FLAGS@` to `-j 2 --offline` to match CRAN's +build environment. The script then reads `src/Makevars.in` (or +`src/Makevars.win.in` on Windows), replaces all placeholders via `gsub()`, +and writes the result to `src/Makevars` (or `src/Makevars.win`). + +## Cleanup scripts + +### `cleanup` and `cleanup.win` + +These scripts are run by R when the package is uninstalled. They remove the +generated `Makevars` files, which are produced fresh at each build and should +not persist after uninstallation. + +```{.bash filename="inst/templates/cleanup"} +rm -f src/Makevars +``` + +```{.bash filename="inst/templates/cleanup.win"} +rm -f src/Makevars.win +``` + +## R templates + +### `extendr-wrappers.R` + +This template is written only once β€” with `overwrite = FALSE` hardcoded β€” +meaning it is only created if it does not already exist. Subsequent updates to +the file are handled automatically by the `document` binary at build time. The +initial template contains the `@useDynLib` directive needed to register the +package's compiled routines with R, plus the scaffolded `hello_world()` wrapper: + +```{.r filename="inst/templates/extendr-wrappers.R"} +# Generated by extendr: Do not edit by hand +# nolint start + +#' @usage NULL +#' @useDynLib {{{pkg_name}}}, .registration = TRUE +NULL + +#' Return string `"Hello world!"` to R. +#' @export +hello_world <- function() .Call(wrap__hello_world) + +# nolint end +``` + +After the first `devtools::document()` call, this file is fully regenerated by +`document.rs` and the initial template content is replaced. diff --git a/contributing/testing-code.qmd b/contributing/testing-code.qmd new file mode 100644 index 0000000..60f53f6 --- /dev/null +++ b/contributing/testing-code.qmd @@ -0,0 +1,4 @@ +--- +title: "Testing Code" +engine: knitr +--- \ No newline at end of file diff --git a/contributing/user-guide-pages.qmd b/contributing/user-guide-pages.qmd new file mode 100644 index 0000000..cb4c23d --- /dev/null +++ b/contributing/user-guide-pages.qmd @@ -0,0 +1,50 @@ +--- +title: "User Guide Pages" +engine: knitr +--- + +User guide pages are friendly introductions to specific features of the +`extendr-api` crate. Keep them **concise** and use **simple language**. +Always use code chunks for extendr code β€” never paste raw snippets. + +### Page structure + +Each page should follow this rough structure: + +````markdown +--- +title: "An informative title" +--- + +- Motivate the functionality the page covers +- No more than 4 sentences + +## The feature + +- Describe the functionality +- Discuss prerequisites if any + +## Basic usage + +- Discuss the code before showing it + +```{extendrsrc} +// your Rust code +``` + +- Summarize and explain the output + +## Advanced usage + +- Motivate the advanced example + +```{extendrsrc} +// your Rust code +``` + +- Summarize and explain + +## See also + +- Links to related pages or external docs +```` \ No newline at end of file diff --git a/contributing/writing-code.qmd b/contributing/writing-code.qmd new file mode 100644 index 0000000..c591d6c --- /dev/null +++ b/contributing/writing-code.qmd @@ -0,0 +1,16 @@ +--- +title: "General writing conventions" +engine: knitr +--- + +This page covers code formatting conventions for the extendr project. Following +these conventions keeps code consistent and readable across contributors. + +## R code + +- Never use `print()` to display an R object. Let R print it implicitly. +- Use the `extendrsrc` and `extendr` knitr engines for all extendr code. See + `user-guide/serde-integration.qmd` for a worked example to emulate. + +## Rust code + From 5cf70cd5f671a2110d9a4942cb46f51643d16ef5 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:09:11 -0600 Subject: [PATCH 06/20] point to contributor guide --- CONTRIBUTING.md | 168 ++++++++++++++++++------------------------------ 1 file changed, 63 insertions(+), 105 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1cac98..047df3d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,139 +1,97 @@ # Contributing -We welcome contributions to the extendr project. Contributions come in many forms. Please carefully read and follow these guidelines. This will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing this project. +Contributions to the extendr project can come in many forms, and we welcome all +of them. Please carefully read and follow the guidelines in our +[Contributing Guide](https://extendr.github.io/contributing/index.html). This +will help us make the contribution process easy and effective for everyone +involved. It also communicates that you agree to respect the time of the +developers managing and developing this project. +## Code of Conduct {#code-of-conduct} -## Quicklinks +We take our open source community seriously and hold ourselves and other +contributors to high standards of communication. By participating and +contributing to this project, you agree to uphold our [Code of +Conduct.](https://github.com/extendr/extendr/blob/master/CODE-OF-CONDUCT.md) -* [Code of Conduct](#code-of-conduct) -* [Getting Started](#getting-started) - * [Issues](#issues) - * [Pull Requests](#pull-requests) -* [Getting Help](#getting-help) -* [Authorship](#authorship) -* [Attribution](#attribution) +## Getting Started {#getting-started} -## Repo structure - -This is the source of the extendr project's website. - -- `user-guide` Is the user guide for using `extendr-api` -- `blog` is the source code for blogs for the extendr-project -- `intro-rust` contains souce code for an Introduction to Rust for R developers - -## Using extendr code chunks - -Whenever using extendr code, use the `extendrsrc` and `extendr` knitr engines. -The user-guide/serde-integration.qmd is the best example. Try to emulate this style. - -## User guide - -The user guide is a friendly introduction to the `extendr-api` crate. Any entries into the user guide should be **concise** and use **simple language**. Always use code chunks whenever using extendr code. - -The structure is roughly as follows: - -```` -``` ---- -title: "An informative title" ---- - -- Motivate the functionality that the entry will cover -- No more than 4 sentences - -## The feature - -- Describe the functionality that will be covered -- Discuss pre-reqs if any - -## Basic usage - -- Provide a basic example -- Discuss your code prior to showing the code - -```{extendrsrc} -// your rust code -``` - -- Summarise and explicate - -## Advanced usage - -- Motivate advanced usage -- Provide an example - -```{extendrsrc} -// your rust code -``` - -- Summarize and explicate - -## See also - -- Provide useful links to other docs on the site or externally -``` -```` - -### Writing style - -- use of second person is fine, but you cannot write about one's intention in the second person. -- Banned phrases ❌: - - "you want to" - - "you need to" -- never use `print()` to print an R object - - -## Code of Conduct - -We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct.](https://github.com/extendr/extendr/blob/master/CODE-OF-CONDUCT.md) - -## Getting Started - -Contributions can be made via Issues and Pull Requests (PRs). A few general guidelines cover both: +Contributions can be made via Issues and Pull Requests (PRs). A few general +guidelines cover both: - Please search for existing Issues and PRs before creating your own. -- We work hard to makes sure issues are handled in a timely manner but, depending on the problem and maintainer availability, it could take a while to investigate the problem. A friendly ping in the comment thread can help draw attention if an issue has not received any attention for a while. Please keep in mind that all contributors to this project are volunteers and may have other commitments they need to attend to. +- We work hard to makes sure issues are handled in a timely manner but, + depending on the problem and maintainer availability, it could take a while + to investigate the problem. A friendly ping in the comment thread can help + draw attention if an issue has not received any attention for a while. + Please keep in mind that all contributors to this project are volunteers and + may have other commitments they need to attend to. -### Issues +### Issues {#issues} -Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created. Please **do not** use Issues to request user support. +Issues should be used to report problems with the library, request a new +feature, or to discuss potential changes before a PR is created. Please **do +not** use Issues to request user support. -Whenever possible, please provide a minimal reproducible example (reprex) to any bug report that you are filing. The more minimal your example, the more likely that somebody else can figure out what the problem is, so please remove any code that isn't relevant to the problem you are reporting. +Whenever possible, please provide a minimal reproducible example (reprex) to any +bug report that you are filing. The more minimal your example, the more likely +that somebody else can figure out what the problem is, so please remove any code +that isn't relevant to the problem you are reporting. -Please keep issues focused on one particular problem. Don't feel shy about opening multiple issues if you're encountering more than one problem. +Please keep issues focused on one particular problem. Don't feel shy about +opening multiple issues if you're encountering more than one problem. -If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. +If you find an Issue that addresses the problem you're having, please add your +own reproduction information to the existing issue rather than creating a new +one. Adding a +[reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) +can also help indicate to our maintainers that a particular problem is affecting +more than just the reporter. -### Pull Requests +### Pull Requests {#pull-requests} -PRs are always welcome and can be a quick way to get your fix or improvement slated for the next release. However, please always open an Issue before submitting a PR. +PRs are always welcome and can be a quick way to get your fix or improvement +slated for the next release. However, please always open an Issue before +submitting a PR. In general, PRs should: - Address a single concern in the least number of changed lines as possible. -- Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both. +- Only fix/add the functionality in question **OR** address wide-spread + whitespace/style issues, not both. - Add unit or integration tests for fixed or changed functionality. - Include documentation. -- Indicate which Issue they address by using the words `Closes #` or `Fixes #` in the body of the PR and/or the git commit message. (See the [GitHub Documentation](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for details about linking PRs to Issues and automatically closing Issues when merging PRs.) - +- Indicate which Issue they address by using the words `Closes #` + or `Fixes #` in the body of the PR and/or the git commit + message. (See the [GitHub Documentation](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) + for details about linking PRs to Issues and automatically closing Issues when + merging PRs.) -In general, we follow the [GitHub flow](https://guides.github.com/introduction/flow/index.html) development model: +In general, we follow the [GitHub flow](https://guides.github.com/introduction/flow/index.html) +development model: 1. Fork the repository to your own Github account 2. Clone the project to your machine 3. Create a branch locally with a succinct but descriptive name 4. Commit changes to the branch 5. Push changes to your fork -6. Open a PR in our repository and follow the PR template so that we can efficiently review the changes. +6. Open a PR in our repository and follow the PR template so that we can + efficiently review the changes. -## Getting Help +## Getting Help {#getting-help} -Please join us on our [Discord server](https://discord.gg/7hmApuc) for general conversations and questions that don't belong into a GitHub issue. +Please join us on our [Discord server](https://discord.gg/7hmApuc) for general +conversations and questions that don't belong in a GitHub issue. -## Authorship +## Authorship {#authorship} -Contributors who have made multiple, sustained, and/or non-trivial contributions to the project may be added to the author list. New author names will always be added at the end of the list, so that author order reflects chronological order of joining the project. All authorship decisions are at the discretion of the current maintainers of the project. +Contributors who have made multiple, sustained, and/or non-trivial contributions +to the project may be added to the author list. New author names will always be +added at the end of the list, so that author order reflects chronological order +of joining the project. All authorship decisions are at the discretion of the +current maintainers of the project. -## Attribution +## Attribution {#attribution} -This document was adapted from the [General Contributing Guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) of the auth0 project. +This document was adapted from the [General Contributing Guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) +of the auth0 project. \ No newline at end of file From 23d8f998310e4f9e1c35f25f5c2398f3a42a93ec Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:10:56 -0600 Subject: [PATCH 07/20] add new and missing images --- images/extendr-gears-logo.png | Bin 0 -> 932 bytes images/extendr-gears-logo.svg | 1 + images/is-odd.png | Bin 0 -> 340462 bytes images/ra-logo-square.svg | 88 ++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 images/extendr-gears-logo.png create mode 100644 images/extendr-gears-logo.svg create mode 100644 images/is-odd.png create mode 100644 images/ra-logo-square.svg diff --git a/images/extendr-gears-logo.png b/images/extendr-gears-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3998c1bb91e7a311710345530673c4d1e541b1b5 GIT binary patch literal 932 zcmV;V16%xwP)KE0<}zNV zNw${pJWaB=tG<&XjgEn9U;?axbD#iR`9zOPzz62QYZwI&|4blFvU6YzEPzMAJBF-* z92fyZ;315H()Q75i-JBdmES&$P~MnZ);!?NAd}k^G(3K9>oQ)0d9bm%;|BT z?Xj4c2+=%@f*m3@4|v#jodYwiAWgC%>S#*fHjILmv4JVOM`mthhd|{4IrF~!Oo0-Z zgi)}lGfI=J50qgP6qdE2=s7p0Cn)c2bWE58VwyWI0sqIZFF)%BTCf3T@6UHDA*i#) z#4L=07{D>)Bzisp3~LzXgFaM_#93-8z#V|MYd0~rr1z~ zQQ$k$-I%Pej%D!vd{+vQI=tRsIke21y4^I%<__?|Zp3?%I>(a8X_j$Nm9WwyIz3~( z1U?)IIKL)*b1VTtz0`tA<#CHnKa7G{2VwoprX9Udu-^-==&IkeQ`p{yPwbXm*CKET zGwj7{F1lP}E(cy=w;yoX_HY`yNcDl$v4u%W_>CBHX?plVH!)^QHr&Hrp&OE~Y#Uk)Q?6>u>&Dt%MjqQf;j{4T?*War%SiWf7qoXq z`;d$5jF!8m%j5JOb*I}NryG>}_2cvrcsk~B+8O`*dHxq(X-9i1|3fVR0000 \ No newline at end of file diff --git a/images/is-odd.png b/images/is-odd.png new file mode 100644 index 0000000000000000000000000000000000000000..37851922368089d32cd048861fd427844b8fbf6f GIT binary patch literal 340462 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[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

Y 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 literal 0 HcmV?d00001 diff --git a/images/ra-logo-square.svg b/images/ra-logo-square.svg new file mode 100644 index 0000000..fe1c1fa --- /dev/null +++ b/images/ra-logo-square.svg @@ -0,0 +1,88 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From 50fa287795fe2c8f0ca75bacd64b4088f8f19340 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:12:23 -0600 Subject: [PATCH 08/20] update intro-rust --- intro-rust/collections.qmd | 126 +++++++------------ intro-rust/control-flow.qmd | 87 +++++++++++++ intro-rust/enumerations.qmd | 199 +++++++++++++++++++++++++++++ intro-rust/enums.qmd | 192 ---------------------------- intro-rust/fizz-buzz.qmd | 78 ------------ intro-rust/for-loops.qmd | 128 ------------------- intro-rust/functions.qmd | 44 +++++++ intro-rust/hello-world.qmd | 184 ++++++++++++--------------- intro-rust/implementations.qmd | 125 +++++++++++++++++++ intro-rust/index.qmd | 63 +++++++--- intro-rust/is-odd.qmd | 88 ------------- intro-rust/iter-map.qmd | 208 ------------------------------- intro-rust/iterators.qmd | 208 ++++++++++++++++++------------- intro-rust/loops.qmd | 81 ++++++++++++ intro-rust/mutability.qmd | 156 ++++++++++++++++------- intro-rust/mutable-vectors.qmd | 105 ---------------- intro-rust/options.qmd | 83 ------------ intro-rust/ownership.qmd | 199 +++++++++++++++++++++++++++++ intro-rust/parallelize.qmd | 15 --- intro-rust/references-slices.qmd | 190 ---------------------------- intro-rust/struct-methods.qmd | 193 ---------------------------- intro-rust/structs.qmd | 145 --------------------- intro-rust/structures.qmd | 168 +++++++++++++++++++++++++ intro-rust/types.qmd | 157 +++++++++-------------- intro-rust/why-rust.qmd | 34 ----- 25 files changed, 1363 insertions(+), 1893 deletions(-) create mode 100644 intro-rust/control-flow.qmd create mode 100644 intro-rust/enumerations.qmd delete mode 100644 intro-rust/enums.qmd delete mode 100644 intro-rust/fizz-buzz.qmd delete mode 100644 intro-rust/for-loops.qmd create mode 100644 intro-rust/functions.qmd create mode 100644 intro-rust/implementations.qmd delete mode 100644 intro-rust/is-odd.qmd delete mode 100644 intro-rust/iter-map.qmd create mode 100644 intro-rust/loops.qmd delete mode 100644 intro-rust/mutable-vectors.qmd delete mode 100644 intro-rust/options.qmd create mode 100644 intro-rust/ownership.qmd delete mode 100644 intro-rust/parallelize.qmd delete mode 100644 intro-rust/references-slices.qmd delete mode 100644 intro-rust/struct-methods.qmd delete mode 100644 intro-rust/structs.qmd create mode 100644 intro-rust/structures.qmd delete mode 100644 intro-rust/why-rust.qmd diff --git a/intro-rust/collections.qmd b/intro-rust/collections.qmd index afd4285..c39a98a 100644 --- a/intro-rust/collections.qmd +++ b/intro-rust/collections.qmd @@ -1,101 +1,63 @@ -# Arrays and Vectors - -::: callout-tip - -## Objective - -Learn how to store multiple values of the same type in arrays and vectors. Understand the difference between arrays and vectors. -::: - - -## Arrays - -Arrays in Rust are fixed in size and hold values of the same type. Since the size is known ahead of time, it makes them fast but inflexible. +--- +title: "Collections" +engine: knitr +--- + +Rust has two primary ways to store multiple values of the same type: arrays and +vectors. Arrays are fixed in size β€” their length is known at compile time, which +makes them fast but inflexible. You cannot add or remove elements after +creation. Vectors are like growable arrays, making them more flexible and also +more common in everyday Rust code. The syntax for creating arrays and vectors +also differs: ```rust -fn main() { - let arr = [10, 20, 30, 40]; - println!("Array: {:?}", arr); -} -``` +// array +let a = [10, 20, 30, 40]; -::: callout-note -The `{:?}` syntax is used for a [**Debug**](https://doc.rust-lang.org/std/fmt/trait.Debug.html) representation of a variable. Using `{}` is used for [**Displaying**](https://doc.rust-lang.org/std/fmt/trait.Display.html) data. - -More often than not, using `{:?}` will be your best option. -::: - -* Arrays use square brackets: `[1, 2, 3]` -* Their size is known at compile time. -* You can't add or remove elements. -* Mostly used when performance is critical and size is known. - -The type of an array is specified as `[type; length]`, e.g. - -```rust -let arr: [i32; 4] = [10, 20, 30, 40]; +// vector +let v = vec![10, 20, 30, 40]; ``` -## Vectors - -Vectors are like growable arrays. They are much more common in everyday Rust code. To create a vector with known values, use the `vec![]` macro. Like an array, they must all be the same type. +Here, the array is created using square brackets, the vector by calling the +`vec!` macro. In this case, the types are inferred. If we want to make those +explicit, the syntax also differs: ```rust -fn main() { - let v = vec![1, 2, 3, 4, 5]; - println!("Vector: {:?}", v); -} -``` - -The type of a vector is specified using `Vec` where `T` is shorthand for any type. - -An empty vector can be created using `Vec::new()` or `vec![]`. If creating an empty vector, the type must be inferred or made explicit. Vectors also have **methods**. Two handy ones are `.len()` for length, and `.is_empty()` (equivalent of `.len() == 0`) +// array +let a: [i32; 4] = [10, 20, 30, 40]; - - -::: callout-important -## Cannot compile -```rust -fn main() { - let x = Vec::new(); - println!("x is empty: {}", x.is_empty()); -} +// vector +let v: Vec = vec![10, 20, 30, 40]; ``` -::: -This cannot compile because the type of `x` is not known. Rust can infer the type if the vector is used elsewhere where the type is known. To make it compile we must specify the type. - -```rust -fn main() { - let x: Vec = Vec::new(); - println!("x is empty: {}", x.is_empty()); -} -```` +In this example, the array type and length are both declared with +`[type; length]`, but the vector only requires specifying that it is a vector of +32-bit integers or `Vec`, which can have any given length. +One nice thing about both vectors and arrays is that you can initialize them to +a specific constant value and size. The syntax here is similar, though again the +vector version requires the use of the `vec!` macro: -## Exercise +```rust +// array +let a: [i32; 4] = [0; 4]; -- Create an array of 4 integers and print it. -- Create a vector with 5 numbers and print it using `{:?}`. -- Compare the length of the array and the vector. -- Bonus: create an empty i32 vector. +// vector +let v: Vec = vec![0; 4]; +``` -### Solution +In each case, we put the initial value `0` followed by the size `4`. -

-View solution +One last thing to note here, specifically about vectors. An empty vector can +also be created with `Vec::new()` or `vec![]`. When creating an empty vector, +however, Rust needs to know the element type either from context or from an +explicit type annotation, since it cannot infer type from the empty vector +itself: ```rust -fn main() { - let arr = [1, 2, 3, 4]; - println!("Array: {:?}", arr); - - let v = vec![10, 20, 30, 40, 50]; - println!("Vector: {:?}", v); - - // Bonus: - let v = vec![42_i32; 0]; -} +let v: Vec = Vec::new(); ``` -
+Vectors also have a number of useful **methods**: `.len()` returns the number of +elements and `.is_empty()` returns `true` if there are no elements. We will +encounter more methods like these as we work through the guide. diff --git a/intro-rust/control-flow.qmd b/intro-rust/control-flow.qmd new file mode 100644 index 0000000..b20c833 --- /dev/null +++ b/intro-rust/control-flow.qmd @@ -0,0 +1,87 @@ +--- +title: "Control Flow" +engine: knitr +--- + +Control flow refers to the ability to conditionally execute code. As with R, +Rust declares conditions for code execution using `if`, `else` and `else if`. +Those conditions will typically involve logical operators: + +- `==` check equality +- `!=` check inequality +- `!` negate a logical value +- `&&` logical AND comparison +- `||` logical OR comparison + +Logical operators work similarly to R but are not vectorised. They return a +single logical called a `bool` instead and can only take on one of two values: +`true` or `false`. + +Like R, each branch of an `if` in Rust must be delimited by curly brackets, but +unlike R, parentheses around the condition are not required: + +```rust +if x == y { + // do something +} else { + // do something else +} +``` + +Note, too, that the result of control flow can be assigned to a variable: + +```rust +let number = if x == y { 5 } else { 3 }; +``` + +Given what we learned about Rust's strong typing system, this suggests an +additional strong constraint on control flow: each branch or "arm" of an `if` +expression must return the same type. Otherwise, the type would be unknown at +compile time. Consider this example program (drawn straight from The Book) that +includes mismatched types in the `if` and `else` arm:. + +```{.rust filename="src/main.rs"} +fn main() { + let condition = true; + + let number = if condition { 5 } else { "six" }; + + println!("The value of number is: {number}"); +} +``` + +If you try to compile and run this program, you will get the following error +message from cargo: + +```default +error[E0308]: `if` and `else` have incompatible types + --> src/main.rs:4:44 + | +4 | let number = if condition { 5 } else { "six" }; + | - ^^^^^ expected integer, found `&str` + | | + | expected because of this + +For more information about this error, try `rustc --explain E0308`. +``` + +As an side, this is actually a great example of one of Rust's best features. Not +only does the error message tell you where precisely it occurs, but it also +points you to additional information to help you understand what is wrong in +your code. Often, it will even suggest how to fix your problem! + +For completeness, here is the correct way to program that control flow: + +```{.rust filename="src/main.rs"} +fn main() { + let condition = true; + + let number = if condition { 5 } else { 6 }; + + println!("The value of number is: {number}"); +} +``` + +```default +The value of number is: 5 +``` \ No newline at end of file diff --git a/intro-rust/enumerations.qmd b/intro-rust/enumerations.qmd new file mode 100644 index 0000000..78adf89 --- /dev/null +++ b/intro-rust/enumerations.qmd @@ -0,0 +1,199 @@ +--- +title: "Enumerations" +engine: knitr +--- + +Sometimes a value can only take on one of a fixed set of possibilities. In R, +this pattern appears constantly as function arguments. For example, the `cor()` +function has the argument `method = c("pearson", "kendall", "spearman")`. Rust +formalizes this idea with *enumerations* or`enum`s, types that can take on +exactly one of a defined set of *variants*. + +::: {.callout-note} +The tidyverse design style guide has a great section on enums. See +[enumerate options](https://design.tidyverse.org/enumerate-options.html#whats-the-pattern). + +Josiah Parry's blog also has a nice discussion of this. See +[Enums in R: towards type safe R](https://josiahparry.com/posts/2023-11-10-enums-in-r/). +::: + +Like structs, enum names use PascalCase, and variants are created using +`EnumName::Variant`: + +```rust +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +let my_shape = Shape::Triangle; +``` + +## Matching + +How do we actually determine behavior based on a variant? This is done using +*pattern matching*, specifically the keyword `match`, which works similar to +`switch()` in R. The pattern match format uses the syntax `Enum::Variant => action`. +When using `match` each variant much be *enumerated*: + +```{.rust filename="src/main.rs"} +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +fn main() { + let my_shape = Shape::Triangle; + + match my_shape { + Shape::Triangle => println!("A triangle has 3 vertices"), + Shape::Rectangle => println!("A rectangle has 4 vertices"), + Shape::Pentagon => println!("A pentagon has 5 vertices"), + Shape::Hexagon => println!("A hexagon has 6 vertices"), + } +} +``` + +```default +A triangle has 3 vertices +``` + +When you only care about specific variants, use `_` as a catch-all for +everything else: + +```{.rust filename="src/main.rs"} +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +fn main() { + let my_shape = Shape::Triangle; + + match my_shape { + Shape::Hexagon => println!("Hexagons are the bestagons"), + _ => println!("Every other polygon is mid"), + } +} +``` + +```default +Every other polygon is mid +``` + +## Methods + +Enums can have `impl` blocks just like structs. Inside the method, `match self` +branches on the variant: + +```{.rust filename="src/main.rs"} +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +impl Shape { + fn n_vertices(&self) -> i32 { + match self { + Self::Triangle => 3, + Self::Rectangle => 4, + Self::Pentagon => 5, + Self::Hexagon => 6, + } + } +} + +fn main() { + let my_shape = Shape::Triangle; + let n_vertices = my_shape.n_vertices(); + + println!("my shape has {} vertices", n_vertices); +} +``` + +```default +my shape has 3 vertices +``` + +## Missing values + +Because of its memory model and strong safety guarantees, Rust has no concept of +a `NULL` type. Null values are actually quite dangerous and have their own +category of errors and security issues. In fact, [C.A.R. Hoare](https://en.wikipedia.org/wiki/Tony_Hoare), +who invented null pointers, called them his "billion-dollar mistake." Rust, +therefore, makes it impossible to use null values, requiring instead that the +concept of missingness be implemented using a special kind of `enum` known as an +`Option<>`, which has exactly two variants: + +```rust +enum Option { + Some(T), + None, +} +``` + +As a reminder, the `T` here stands for any generic type. When a value is +present, it is wrapped in `Some(T)`. When it is absent, the variant is `None`. +The type system forces you to acknowledge the possibility of `None` before you +can use the inner value β€” there is no way to accidentally treat a missing value +as if it were present. + +Since `Option` is just an enum, we can match on the variants to access its +values. + +```rust +// create an Option that contains a value +let measure = Some(Measure::Euclidean); + +match measure { + Some(v) => println!("The measure is: {}", v), + None => println!("Oh no! The measure is missing!"), +} +``` + +This makes missing values visible - the code cannot compile unless both arms are +handled. + +::: {.callout-warning} +Sometimes dealing with options is a headache, particularly when we're in the +early stages of developing. While we can use `.unwrap()` or `.expect()` to grab +the inner value of an option _without matching_, this is **dangerous!!** because +we are ignoring the possibility of a `None`. Unwrapping on a `None` leads to a +**panic**. Panics cause your program to abort. + +```rust +thread 'main' panicked at src/main.rs:4:41: +called `Option::unwrap()` on a `None` value +``` +::: + +## Optional arguments + +A common use is to make function arguments optional β€” call with a value when you +have one, and fall back to a default when you do not, similar to how we use +`function(x = NULL)` in R. This can be achieved using an option in Rust. + +```{.rust filename="src/main.rs"} +fn greet_user(name: Option) { + let user_name = name.unwrap_or(String::from("world")); + println!("Hello, {}!", user_name); +} + +fn main() { + greet_user(None); // Output: Hello, world! + greet_user(Some("Ferris".to_string())); +} +``` + +```default +Hello, world! +Hello, Ferris! +``` diff --git a/intro-rust/enums.qmd b/intro-rust/enums.qmd deleted file mode 100644 index 6120f8c..0000000 --- a/intro-rust/enums.qmd +++ /dev/null @@ -1,192 +0,0 @@ -# Enum(eration)s - -::: callout-tip -## Objective - -Understand what `enum`s are and when you may want to use one. - -::: - -Oftentimes a variable can only take on a small number of values. This is when an **enumeration** (enum) would be useful. - -`enum`s are values that can only take on a select few **variants**. They are defined using the `enum` keyword. - - - -## Enums in R - -We use enums _all the time_ in R and almost exclusively as function arguments. - -::: aside -The tidyverse design style guide has a great section on enums. See [enumerate options](https://design.tidyverse.org/enumerate-options.html#whats-the-pattern). -::: - -```r -args(cor) -``` -```r -function( - x, y = NULL, use = "everything", - method = c("pearson", "kendall", "spearman") # πŸ‘ˆπŸΌ enum! -) -``` - -::: aside -I've written about this in more detail in my blog post [Enums in R: towards type safe R](https://josiahparry.com/posts/2023-11-10-enums-in-r/) -::: - -### Example - -For example, it may make sense to create an `enum` that specifies a possible shape. - -```rust -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} -``` - -## Variant-specific behavior - -The `Shape` enum can take on only one of those 4 **variants**. An enum is created using the format `EnumName::Variant`. - -How do we actually determine behavior based on a variant? This is done using **pattern matching**. - -The keyword `match` lets us perform an action based on an enum's variant. It works by listing each variant and the behavior to that happens when that variant is matched. - -The pattern match format uses the syntax `Enum::Variant => action`. When using `match` each variant much be _enumerated_: - -::: aside -`=>` is often referred to as "fat arrow." But if you say "chompky" I'll also get it. -::: - -```rust -match my_shape { - Shape::Triangle => todo!(), - Shape::Rectangle => todo!(), - Shape::Pentagon => todo!(), - Shape::Hexagon => todo!(), -} -``` - -::: callout-tip -todo!() is a placeholder that can be used to make the compiler happy -::: - - -### Example - -For example we may want to print the number of verticies of a shape: - -```rust -let my_shape = Shape::Triangle; - -match my_shape { - Shape::Triangle => println!("A triangle has 3 vertices"), - Shape::Rectangle => println!("A rectangle has 4 vertices"), - Shape::Pentagon => println!("A pentagon has 5 vertices"), - Shape::Hexagon => println!("A hexagon has 6 vertices"), -} -``` - -## Wildcard matching - -Sometimes we want to customize behavior on only a subset of variants. We can use a catch all in the match statement `_ =>` use the underscore to signify "everything else". - -```rust -match my_shape { - Shape::Hexagon => println!("Hexagons are the bestagons"), - _ => println!("Every other polygon is mid"), -} -``` - -## Enums can `impl`, too - -Enums can have methods too just like a struct using `impl` keyword - -```rust -impl Shape { - fn is_bestagon(&self) -> bool { - match self { - Self::Hexagon => true, - _ => false - } - } -} -``` - - -## Exercise 1 - -- Create an enum called `Measure` with two variants `Euclidean` and `Haversine` -- Create a method called `ndim()` which returns `2` for `Euclidean` and `3` for `Haversine` - -
- View solution - -```rust -enum Measure { - Euclidean, - Haversine, -} - -impl Measure { - fn ndim(&self) -> i32 { - match self { - Self::Euclidean => 2, - Self::Haversine => 3 - } - } -} -``` - -
- -## Exercise 2 - - -- Create a new method `distance()` for `Point` struct that returns an `f64` - - Arguments: `&self`, `destination: &Self`, `measure: &Measure` -- When `measure` is `Euclidean` use the `euclidean_distance()` method -- When the variant is `Haversine` use the `haversine_distance()` method - -The haversine method is defined as: - -
-Code for `haversine_distance()` -```rust -impl Point { - fn haversine_distance(&self, destination: &Self) -> f64 { - let radius = 6_371_008.7714; // Earth's mean radius in meters - let theta1 = self.y.to_radians(); // Latitude of point 1 - let theta2 = destination.y.to_radians(); // Latitude of point 2 - let delta_theta = (destination.y - self.y).to_radians(); // Delta Latitude - let delta_lambda = (destination.x - self.x).to_radians(); // Delta Longitude - - let a = (delta_theta / 2f64).sin().powi(2) - + theta1.cos() * theta2.cos() * (delta_lambda / 2f64).sin().powi(2); - - 2f64 * a.sqrt().asin() * radius - } -} -``` - -
- - -
-View solution -```rust - -impl Point { - // Demonstrates using pattern matching an enum - fn distance(&self, destination: &Self, measure: &Measure) -> f64 { - match measure { - Measure::Euclidean => self.euclidean_distance(destination), - Measure::Haversine => self.haversine_distance(destination), - } - } -} -``` diff --git a/intro-rust/fizz-buzz.qmd b/intro-rust/fizz-buzz.qmd deleted file mode 100644 index 6ac7531..0000000 --- a/intro-rust/fizz-buzz.qmd +++ /dev/null @@ -1,78 +0,0 @@ -# Control Flow - -::: callout-tip -## Objective - -Understand control flow and numeric operators in Rust. You will create the `FizzBuzz` program using Rust! - -::: - -## Numeric operators - -- `+` addition -- `-` subtraction -- `/` division -- `%` remainder - -## Logical Operators: - -Logical operators are quite similar to R. The difference is that these operations are not vectorised. Furthermore, in Rust, a logical is called `bool` for booleans. `bool`s can take on only two values: `true` or `false`. - -- `==` check equality -- `!=` check inequality -- `!` negate a logical value -- `&&` logical AND comparison -- `||` logical OR comparison - - -## Control flow - -Rust uses `if`, `else`, and `else if` statements just like R. Where each branch is delimted by curly braces. - -::: callout-warning -Each branch of the `if` statement must return the same type. For this portion of the workshop, be sure to **terminate** each statement with `;` inside of the `if` statement so that nothing (unit type) is returned. -::: - - -```rust -if x == y { - // do something -} else { - // do something else -} -``` - -The key difference is that the use of parentheses is not necessary for the conditional statement. - - -## Exercise - -This exercise you will create the famous `FizzBuzz` program. - -For this, create a variable `i`. The rules are: - -- when `i` is a multiple of `3`, print `Fizz` -- when `i` is a multiple of `5`, print `Buzz` -- when `i` is a multiple of _both_ `3` and `5`, print `FizzBuzz` - - -### Solution - -
-View solution -```rust -fn main() { - // let i = 15; // FizzBuzz - // let i = 3; // Fizz - // let i = 5; // Buzz - let i = 47; // Nothing - if (i % 3 == 0) && (i % 5 == 0) { - println!("FizzBuzz"); - } else if i % 3 == 0 { - println!("Fizz"); - } else if i % 5 == 0 { - println!("Buzz"); - } -} -``` -
diff --git a/intro-rust/for-loops.qmd b/intro-rust/for-loops.qmd deleted file mode 100644 index 48c843f..0000000 --- a/intro-rust/for-loops.qmd +++ /dev/null @@ -1,128 +0,0 @@ -# `for`-loops - -::: callout-tip - -## Objective - -Be able to iterate over a collection of values. Apply logic inside of the for loop. - -::: - -## `for` loop syntax - -In Rust, `for` loops are the easiest way to go over each item in a vector or array. - -```rust -for value in collection { - // do something with value -} -``` - -When we iterate through a collection, each item takes on the value of the name you provide before `in`. For example `for x in my_vec` creates a binding to `x` at each iteration. - -```rust -fn main() { - let nums = vec![1, 2, 3]; - for n in nums { - println!("n is: {}", n); - } -} -``` - - -## Scope - -R has some of the most flexible and unique scoping rules. Rust isn't as kind. The rules are essentially: - -* Values *outside* of the for loop are accessible inside of it. -* Values created *inside* of the for loop cannot be accessed outside of it. - - -### Example: Outer value used inside loop - -:::{.callout-tip title = "This does compile" icon=false} -```rust -fn main() { - let greeting = "Hi"; - let names = vec!["Alice", "Bob"]; - - for name in names { - println!("{greeting}, {name}!"); - } -} -``` -::: - -### Example: Inner value not usable outside loop - -::: callout-important -## This does not compile! - - -```rust -fn main() { - let numbers = vec![1, 2, 3]; - - for n in numbers { - let doubled = n * 2; - println!("{n} doubled is {doubled}"); - } - - // ❌ `doubled` doesn't exist here - // println!("Last doubled: {}", doubled); -} -``` -::: - - -## Exercise - -Using a vector of integers, write a loop that prints: - -* "Fizz" if divisible by 3 -* "Buzz" if divisible by 5 -* "FizzBuzz" if divisible by both -* The number otherwise - -Use this vector: `vec![1, 2, 3, 4, 5, 15]` - -### Solution - -
-View hint - -```rust -fn main() { - let nums = vec![1, 2, 3, 4, 5, 15]; - - for n in nums { - // add fizz-buzz logic here referring to `n` - } -} -``` - -
- - -
-View solution - -```rust -fn main() { - let nums = vec![1, 2, 3, 4, 5, 15]; - - for n in nums { - if n % 15 == 0 { - println!("FizzBuzz"); - } else if n % 3 == 0 { - println!("Fizz"); - } else if n % 5 == 0 { - println!("Buzz"); - } else { - println!("{}", n); - } - } -} -``` - -
diff --git a/intro-rust/functions.qmd b/intro-rust/functions.qmd new file mode 100644 index 0000000..26b4665 --- /dev/null +++ b/intro-rust/functions.qmd @@ -0,0 +1,44 @@ +--- +title: "Functions" +engine: knitr +--- + +So far, we have been modifying the `main()` function, but Rust lets us define as +many functions as we need. In R, functions are objects created with +`<- function()`. In Rust, they are declared with the `fn` keyword. Just like +everything else in Rust, arguments are typed - each argument takes the form +`arg_name: type`, and the return type is specified after `->`. + +```rust +fn name_of_function(arg1: ArgType) -> ReturnType { + // function body + my_return_object +} +``` + +As in R, the last expression in a function body is returned automatically β€” no +explicit `return` keyword is needed unless you are returning early. Functions +can also be declared anywhere in the file, outside of `main()`. + +Identifying whether a number is odd or even is a classic example. The +[is-odd npm package](https://www.npmjs.com/package/is-odd) became famous for +being a remarkably small and widely depended-upon piece of code: + +![](/images/is-odd.png){width=70% fig-align="center"} + +Here is the Rust equivalent. We define `is_even()` first, taking an `i32` and +returning a `bool`, then use it inside `is_odd()`: + +```rust +fn is_even(x: i32) -> bool { + x % 2 == 0 +} + +fn is_odd(x: i32) -> bool { + !is_even(x) +} +``` + +Since `x % 2 == 0` is an expression that evaluates to a `bool`, it is returned +directly without a `return` statement. Building new functions from existing ones +like this is idiomatic Rust. diff --git a/intro-rust/hello-world.qmd b/intro-rust/hello-world.qmd index cd43500..a43e748 100644 --- a/intro-rust/hello-world.qmd +++ b/intro-rust/hello-world.qmd @@ -1,128 +1,108 @@ -# Hello, World! - - -::: callout-tip -## Objective - -Learn how to create a new Rust crate and understand its file structure. You will know how to use `cargo run` and the `println!()` macro. -::: - - -Rust uses a tool called `cargo` for building, checking, and managing dependencies. This is installed for you when you used `rustup` to install Rust. - -To create a new Rust crate, use `cargo new name-of-crate`. This creates a new directory called `name-of-crate`. Be sure to `cd` into that directory. - -::: aside -`cd` is short for change directory. We use this to...yup...change the directory from the terminal. -::: - -## Crate anatomy - -Two types of crates: binary, library. - -::: aside -This first workshop we will work only with a binary crate. We will create a library in the second half of the day. -::: - -Binary crates are standalone applications like command line tools, or things that run onceβ€”simiar to a script that you run with `Rscript main.R` - - -## Crate anatomy - -A new crate looks like this: - -``` -intro-to-rust/ -β”œβ”€β”€ Cargo.toml # Metadata & dependencies (like DESCRIPTION) -β”œβ”€β”€ Cargo.lock # Dependency versions (like renv.lock) +--- +title: "Hello, World!" +engine: knitr +--- + +In this tutorial, you will learn how to setup a new Rust crate and get a sense +for what it includes. You will also learn how to build and run your new crate +with `cargo`, a package manager and build system for Rust. You do not need to +install `cargo` because it comes bundled with Rust. + +When you first setup a Rust crate, you will have to choose between one of two +types: binary crates and library crates. Binary crates are standalone +applications or executables that you can run, like command line tools. Library +crates are typically small bundles of code that are used in the development of +other crates. In this tutorial, we will focus on building a binary crate. + +To create a new binary crate, open a terminal and run the following: + +```default +$ cargo new hello-world +``` + +This will create a new directory called `hello-world` that you can then navigate +into using `cd hello-world`. Once inside that directory, you will find all of +the following: + +``` +hello-world/ +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ Cargo.lock └── src/ - └── main.rs # Entry point β€” like main.R + └── main.rs ``` +The `Cargo.toml` file is a document in which you specify metadata and +dependencies for your crate similar to an R package's `DESCRIPTION`. The +`Cargo.lock` file maintains copies of those dependencies, similar to +`renv.lock`. A lot more can be said about those two files, which are absolutely +crucial to programming in Rust, but for now let's focus on the Rust file in the +source directory. If you open that file in Positron (or your preferred IDE), you +will see the following text: -## `main.rs` - -When you create a new rust binary the file `src/main.rs` is prepopulated with: - -::: callout-tip -## `src/main.rs` - -The `main()` function defines what is executed when your _binary_ is run. - - -```rust +```{.rust filename="src/main.rs"} fn main() { println!("Hello, world!"); } ``` -::: +In just those three short lines, we see examples of many Rust coding patterns: -There are a few things going on in here: +- Functions are declared using the `fn` keyword, +- The `main()` function defines what happens when you run your binary, +- Blocks of code are delimited using curly brackets (just like R), and +- Statements end with `;`. -- Functions are declared using the `fn` keyword -- The `main()` function is the entrypoint of the program (and required) -- Blocks of code are delimted using curly braces (like R & C) -- Statements end with `;` -- `println!()` is a macro (notice the `!`) which is used to print to `stdout` +Right now, our app uses the `println!()` macro to print "Hello, world!" to the +terminal. When a program writes to the console, it does so through file +connections called standard output (`stdout`) and standard error (`stderr`). In +R, when we print a message with `print()` or `message()`, we print to stdout. +Similarly, when we make a warning or error using `stop()` or `warning()`, that +is writing to `stderr`. The Rust `println!()` macro also has the nice feature of +supporting string interpolation like `sprintf()` and `glue()`: -::: aside -When a program writes to the console it does so through file connections called standard output (`stdout`) and standard error (`stderr`). - -When we print a message with `print()` or `message()` in R, we print to stdout. When we make a warning or error using `stop()` or `warning()` in R, that is writing to `stderr`. -::: - - -::: callout-important - -Variables can only be defined inside of the `main()` function. - -::: - -## `println!()` - -* Macros have a `!`, like `println!()`. -* Think of it like `print()` in R, but explicit. -* It supports format strings: - - -:::{layout-ncol=2} -#### Using placeholder -```rust +``` rust +// indirect interpolation let name = "Josiah"; println!("Hello, {}!", name); -``` -#### Direct interpolation -```rust +// direct interpolation let name = "Josiah"; println!("Hello, {name}!"); ``` -::: - +If we add one or the other of those lines to `main()` like so -## Exercise - -- In your terminal, create a new rust crate called `intro-to-rust` -- Open the new Rust crate's folder in Positron -- Run the hello world program using `cargo run` -- Create a variable called `name` inside of the `main()` function with your name -- Print `Hello, {name}!` using `println!()` -- To run it navigate to your terminal and then enter `cargo run`. - -### Solution - -
-View solution - -In `src/main.rs` - -```rust +```{.rust filename="src/main.rs"} fn main() { let name = "Josiah"; println!("Hello, {name}!"); } ``` -
+we can then call the application and get it to execute in the terminal: + +```default +$ cargo run + Compiling hello_world v0.1.0 (/hello-world) + Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs + Running `target/debug/hello_world` +Hello, Josiah! +``` + +Notice that `cargo` provides detailed information about the build process +(printed to stderr) before actually running the program (and printing to +stdout). This is a useful reminder that Rust, unlike R, is a compiled language - +Rust programs have to be built before they can be run. For those coming from R, +that might sound rather mysterious, and in some respects it is, but we want to +emphasize how easy it was to get a program up and running. It was just three +lines of code in the terminal: + +```default +$ cargo new hello-world +$ cd hello-world +$ cargo run +``` + +That's it! Those three lines were sufficient to build and run our simple binary. +Now we can move on to learning about basic data types in Rust. diff --git a/intro-rust/implementations.qmd b/intro-rust/implementations.qmd new file mode 100644 index 0000000..74acf25 --- /dev/null +++ b/intro-rust/implementations.qmd @@ -0,0 +1,125 @@ +--- +title: "Implementations" +engine: knitr +--- + +Other methods can be implemented for structs using `impl` blocks. An `impl` +block is roughly analogous to defining S3 methods in R, except Rust's `impl` +blocks associate behavior directly with the type definition, making them +object-oriented. The syntax is this: + +```rust +impl TypeName { + fn method_name() { + // body + } +} +``` + +## Associated functions + +An *associated function* is called on the type itself rather than on an +instance. These are written as `TypeName::function()` and are typically used +as constructors. The return type `Self` refers to the type being implemented: + +```{.rust filename="src/main.rs"} +#[derive(Debug)] +struct Person { + name: String, + age: i32 +} + +impl Person { + fn new(name: String, age: i32) -> Self { + Person { name, age } + } +} + +fn main() { + let me = Person::new("Josiah".to_string(), 29); + println!("{me:?}"); +} +``` + +```default +Person { name: "Josiah", age: 29 } +``` + +You have already seen associated functions in this form: `Vec::new()` and +`String::new()` are both associated functions. + +## Self-reference + +To define a method that operates on an instance, add `&self` as the first +argument. This provides a read-only reference to the struct, so you can access +its fields but not move or modify them: + +```rust +impl Person { + fn greet(&self) { + println!( + "Hello, my name is {} and I am {} years old.", + self.name, self.age + ); + } +} +``` + +A method can also accept another instance of the same type by using `&Self` as +an argument type. Here `is_older_than()` compares two `Person` values: + +```rust +impl Person { + fn is_older_than(&self, other_person: &Self) -> bool { + self.age > other_person.age + } +} +``` + +## Mutable self + +To modify a struct's fields from a method, use `&mut self`. Here `rename()` and +`celebrate_birthday()` both mutate fields in place: + +```{.rust filename="src/main.rs"} +#[derive(Debug)] +struct Person { + name: String, + age: i32 +} + +impl Person { + fn new(name: String, age: i32) -> Self { + Person { name, age } + } + + fn greet(&self) { + println!( + "Hello, my name is {} and I am {} years old.", + self.name, self.age + ); + } + + fn rename(&mut self, new_name: &str) { + self.name = new_name.to_string(); + } + + fn celebrate_birthday(&mut self) { + self.age += 1; + } +} + +fn main() { + let mut me = Person::new("Josiah".to_string(), 29); + me.celebrate_birthday(); + me.rename("Jo"); + me.greet(); +} +``` + +```default +Hello, my name is Jo and I am 30 years old. +``` + +Note that the instance must also be declared `mut` when it is assigned to a +variable. \ No newline at end of file diff --git a/intro-rust/index.qmd b/intro-rust/index.qmd index ab28fee..c651308 100644 --- a/intro-rust/index.qmd +++ b/intro-rust/index.qmd @@ -1,35 +1,58 @@ --- -title: Rust for R Developers +title: A Brief Introduction to Rust +page-layout: article +toc: true +toc-depth: 0 +date: today +date-format: long --- -You want to build Rust powered R packages but don't know where to start? +Want to build Rust powered R packages but don't know where to start? This guide +will walk you through the basics of Rust programming using familiar R concepts. -## What is this? +## Why Rust? πŸ¦€ -This is an opinionated and streamlined introduction to Rust to get you building **fast** ⚑️, and **vectorized** R functions within a few hours. +Unlike R and Python, which are *interpreted* languages, Rust is a *compiled* +language. That means it can often times be much, much faster and more efficient, +similar to other compiled languages like C or C++. But where those languages can +be hard to use and easy to break, Rust was built to be safer and more helpful. +Rust is especially good at preventing bugs related to memory. There’s no garbage +collector in Rust, so it is quite memory efficient. Moreover, Rust is designed +with the developer in mind. The Rust compiler provides error messages that rival +β€” or maybe even surpass β€” the quality of tidyverse error messaging. -## Who is it for? - -We get it. Learning Rust can be daunting and frankly, much of the "Intro to Rust" material out there isn't immediately applicable to R or data science. - -This introduction is designed for **intermediate R developers**. +## Prerequisites +This introduction is designed for **intermediate R developers** who are familiar +with fundamental computing concepts, including data types (like floats, +integers, and booleans), iteration (with `for` and `while` loops, `purrr::map()` +style iterators, and the `apply()` family of functions), control flow, and +functions. Being comfortable with a terminal environment will be helpful, too. -## Prerequisites +Regardless of whether you meet those prerequisites, you may find it helpful to +freshen up on your R fundamentals by reading or reviewing the following +materials: +- [R for Data Science](https://r4ds.hadley.nz) +- [Hands-On Programming with R](https://rstudio-education.github.io/hopr) +- [Advanced R](https://adv-r.hadley.nz) -It is recommended that you be familiar with fundamental computing concepts such as: +As for Rust, a lot of the materials in this brief introduction come from the +official guide, [The Rust Programming Language](https://doc.rust-lang.org/book/), +what Rust developers affectionately call "The Book." -- Data types such as floats, integers, and booleans -- Iteration such as for / while loops, `purrr::map()` style iterators, and the `apply()` family of functions -- Control Flow -- Writing functions +## Software requirements -### Resources +This guide assumes that you have Rust {{< var version.rust >}} installed. Please +see [Get Started](get-started.qmd) if you do not. -- Read [Advanced R Chapter 3](https://adv-r.hadley.nz/vectors-chap.html) -- Read [Hands-On Programming with R](https://rstudio-education.github.io/hopr/loops.html), [R for Data Science Chapter 26](https://r4ds.hadley.nz/iteration.html) -- Read [Advanced R Chapter 4](https://adv-r.hadley.nz/control-flow.html) -- Read [R for Data Science Chapter 25](https://r4ds.hadley.nz/functions.html), [Advanced R Chapter 6](https://adv-r.hadley.nz/functions.html) +If you are not yet comfortable with installing Rust, you may find it helpful to +try out examples in this tutorial using the open source +[Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021) +browser app. +## Acknowledgment +These introductory tutorials are based on the +[Rust for R Developers](https://josiahparry.github.io/2025-cascadia-rust-for-r-devs/) +workshop led by Josiah Parry at the 2025 Cascadia R Conference. \ No newline at end of file diff --git a/intro-rust/is-odd.qmd b/intro-rust/is-odd.qmd deleted file mode 100644 index db004ef..0000000 --- a/intro-rust/is-odd.qmd +++ /dev/null @@ -1,88 +0,0 @@ -# Functions - -::: callout-tip -Learn how to define and use your own functions in Rust. -::: - -We've already been modifying the `main()` function this whole time. We can create and use additional functions too. - -In R, functions are yet another type of object that we create using `<- function()`. In Rust, they are declared using `fn` keyword. Just like everything else in Rust, arguments are typed. Arguments take the structure of `arg: type`. - -Much like R, the last object in a function body is return automatically. You do not need to use the `return` keyword unless you are performing an early return. - - - -```rust -fn name_of_function(arg1: ArgType) -> ReturnType { - // function body - my_return_object -} -``` - -## Example - -Identifying if a number is odd or even isn't always so easy! - -![is-odd npm](/images/is-odd.png){width=70%} - -First define a function called `is_even()` that takes an `i32` (integer) and returns a `bool`. - -::: aside -It may not be entirely obvious, but functions can be declared outside of the `main()` function. -::: - -```rust -fn is_even(x: i32) -> bool { - x % 2 == 0 -} -``` - -We can use our already defined function inside of another: - -```rust -fn is_odd(x: i32) -> bool { - !is_even(x) -} -``` - - -## Exercise - -Create a function called `mean()` that calculates the mean of a `Vec`. - -* In `main()`, create a vector `x` with 5 or more `f64` values. -* Call `mean(x)` and print the result. - -::: callout-note -Use `x.len()` to get the length and `as f64` to convert it to a float. -::: - -### Solution - -
-View solution - -```rust -fn mean(x: Vec) -> f64 { - let mut total = 0.0; - let n = x.len(); - for xi in x { - total += xi; - } - total / n as f64 -} - -fn main() { - let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let result = mean(x); - println!("Mean is: {}", result); -} -``` - -
diff --git a/intro-rust/iter-map.qmd b/intro-rust/iter-map.qmd deleted file mode 100644 index 52b2e2a..0000000 --- a/intro-rust/iter-map.qmd +++ /dev/null @@ -1,208 +0,0 @@ -# Mapping over Iterators - -::: callout-tip -## Objective -- Understand how to transform iterator values using `.map()` -- Learn how to use closures (anonymous functions) in Rust -- Collect iterator results back into a collection using `.collect()` -- Use turbofish πŸ πŸ’¨ syntax for type annotations when needed -::: - -Often we want to transform the values in a vector. To do this in Rust, we can use the `.map()` method. `.map()` lets us apply an operation on each element of the iterator. - -In R, we often use the `purrr::map()` function or the `apply()` family of functions to do this. - -## Using `.map()` - -We apply `.map()` to an iterator. For example, to square each element in a vector we can write: - -```rust -// providing explicit type annotation in one -// value so the compiler knows its f64 and not f32 -let x = vec![5.9_f64, 6.8, 4.5, 7.3, 6.2]; - -x.iter().map(|xi| xi.powi(2)) -``` - -Using `.map()` returns another iterator. We can chain operations over iterators by using multiple `.map()` statements. - - -## Closures - -In the above example we used a **closure** to modify each element of the iterator. Closures are Rust's version of an anonymous function. - -A closure takes the structure `|arg| expression`. They can also be multiple lines by wrapping the expression in a braces: - -```rust -x - .iter() - .map(|xi| { - let squared = xi.powi(2); - xi.sqrt() - }) -``` - -Since iterators only contain one item, the closure only has one argument. However, you can use **destructuring** in the closure. For example if using `.enumerate()` we want to access `i` we can do so: - -```rust -x - .iter() - .enumerate() - .map(|(i, xi)| { - // do stuff here - }) -``` - -## Collecting results - -In R when we use `map()` the results are always returned as a vector. In Rust, we have to explcitly **collect** the iterator into our own type using `.collect()`. - -Typically iterators are collected into a `Vec`. Rust cant always infer the type that you want to collect into so we must tell the compiler what type we want. - -We can do this during assignment: - -```rust -let x_squared: Vec = x.iter().map(|xi| xi.powi(2)).collect(); -``` - - -## Turbofish πŸ πŸ’¨ - -Alternatively, we can use "turbofish" syntax. We can specify the type directly in the `.collect()` function. `.collect()` is a **generic** method. And in the words of the Rust standard library documentation (emphasis mine) - -> "Because `collect()` is so general, it **can cause problems with type inference**. As such, `collect()` is one of the few times you’ll see the syntax affectionately known as the β€˜turbofish’: `::<>`. This helps the inference algorithm understand specifically which collection you’re trying to collect into." - -Turbofish takes the structure of `.collect::()`. - - - -### Examples - -:::{layout-ncol="2"} -#### Explicit typing -```rust -fn main() { - let nums = vec![1, 2, 3]; - - // Add 1 to each element - let incremented: Vec<_> = nums.iter() - .map(|x| x + 1) - .collect(); - - println!("{:?}", incremented); -} -``` - -#### Inference w/ turbofish -```rust -fn main() { - let nums = vec![1, 2, 3]; - - // Add 1 to each element - let incremented = nums.iter() - .map(|x| x + 1) - .collect::>(); - - println!("{:?}", incremented); -} -``` -::: - -Turbofish is a bit more awkward at first, but it is more flexible and doesn't require modification whenever the inner type changes. - -## Exercise 1 - -Calculate the **variance** of a slice of `f64` values. - -$$ -\text{variance} = \frac{\sum_{i=1}^n (x_i - \bar{x})^2}{n - 1} -$$ - -* Create a function `variance()` that: - - * Uses `.map()` to calculate squared differences from the mean - * Uses `.sum()` to add them up - * Divides by `n - 1` - -::: callout-tip - -* Use `.powi(2)` to square values. -* Use your previously defined `mean()` function inside `variance()`. -::: - - -
-View hint - -```rust -fn variance(x: &[f64]) -> f64 { - let n = x.len() as f64; - let avg = mean(x); - let sq_diffs: f64 = x - .iter() - .map(|xi| ___ ) // squared difference here - .__(); // sum method here - - sq_diffs / (n - 1.0) -} -``` - -
- -### Solution - -
-View solution - -```rust -fn variance(x: &[f64]) -> f64 { - let n = x.len() as f64; - let avg = mean(x); - let sq_diffs: f64 = x - .iter() - .map(|xi| (xi - avg).powi(2)) - .sum(); - sq_diffs / (n - 1.0) -} - -fn main() { - let x = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]; - println!("Variance is: {:.2}", variance(&x)); -} -``` - -
- - -## Exercise 2 - -Create a function `standardize()` to perform z-score standardization on a vector of `f64`. - -$$ -z_i = \frac{x_i - \mu}{\sigma} -$$ - -* Use `.iter()` and `.map()` to calculate mean and variance. -* Use `.into_iter()`, `.map()`, and `.collect()` to build the standardized vector. -* Return a new `Vec` of standardized values. - -### Solution - -
-View solution - -```rust -fn standardize(x: &[f64]) -> Vec { - let avg = mean(x); - let std_dev = variance(x).sqrt(); - x.iter().map(|xi| (xi - avg) / std_dev).collect() -} - -fn main() { - let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let standardized = standardize(x); - println!("Standardized: {:?}", standardized); -} -``` - -
diff --git a/intro-rust/iterators.qmd b/intro-rust/iterators.qmd index e25ee4c..d0d3588 100644 --- a/intro-rust/iterators.qmd +++ b/intro-rust/iterators.qmd @@ -1,152 +1,184 @@ -# Iterators +--- +title: "Iterators" +engine: knitr +--- -::: callout-tip +So far we have been using `for` loops to work through collections, but Rust has +a more powerful and flexible mechanism: iterators. Iterators produce a sequence +of items that can be accessed one at a time and chained with methods like +`.sum()`, `.min()`, `.max()`, and `.enumerate()`. They are closer in spirit to +`apply()` or `purrr::map()` than to a traditional for-loop. -## Objective - -- Understand what an iterator is and how to create one. -- Learn the difference between `.iter()` and `.into_iter()`. -- Use basic iterator methods like `.sum()`, `.min()`, `.max()`, and `.enumerate()`. - -::: - -So far we have been using a `for` loop to iterate through our items. However, it is not the only way to iterate. Instead, we can use **iterators**. Iterators often are simpler and easier to useβ€”particularly when modifying each element in a `Vec`. - -Iterators can be created from basic collections like vectors or arrays as well as more advanced types such as `HashMap`s. They produce a sequence of items that can be accessed (or modified) one at a time. +And actually, we were technically using iterators all along, since Rust calls +`.into_iter()` on the vector passed to a for-loop under the hood. Both of these +are equivalent: +```rust +let nums = vec![3, 6, 9]; -## Consuming vs. Borrowing +for n in nums { ... } +for n in nums.into_iter() { ... } +``` -When we use a for loop with a collection, we are consuming (moving) the underlying vector. Under the hood, using a for loop actually _creates_ an iterator by calling the `.into_iter()` method. +## Borrowing -`for xi in x` is identical to calling `for xi in x.into_iter()` for example: +One important constraint on the use of `.into_iter()` is that it actually +*consumes* the vector β€” ownership is moved into the iterator so that the vector +can no longer be used afterward: -```rust +```{.rust filename="src/main.rs"} fn main() { let nums = vec![3, 6, 9]; - - for n in nums.into_iter() { + for n in nums.into_iter() { // ⬅️ nums is moved println!("Value: {n}"); } - - // nums has been moved ❌ - // println!("nums: {:?}", nums); + + println!("nums: {:?}", nums); // ❌ compiler error } ``` -Remember: `.into_iter()` *consumes* the original value. +To iterate without consuming the original collection, use `.iter()` instead. +This *borrows* the collection and produces references to each element, leaving +the original value intact: -Alternatively, the `.iter()` method can be called which **borrows** the collection and produces an iterator where each item is a refernce. - -```rust +```{.rust filename="src/main.rs"} fn main() { let nums = vec![3, 6, 9]; - + for n in nums.iter() { - println!("Reference to value: {}", n); + println!("Reference to value: {n}"); } - // nums is still usable βœ… - println!("nums: {:?}", nums); + + println!("nums is still usable: {:?}", nums); } ``` -## Basic Iterator Methods - -Once you have an iterator, there are a number of methods that can be used. Some of the helpful ones are: - -- `.sum()` β€” Add all the values together. -- `.min()` β€” Find the smallest value. -- `.max()` β€” Find the largest value. -- `.enumerate()` β€” Pairs each value with its index. - - -::: aside -The `.min()` and `.max()` methods cannot reliably return the min/max of a `Vec` because of floating point weirdness. Instead they return `Option<>` which we will discuss later. Just _know_ that they exist. -::: +```default +Reference to value: 3 +Reference to value: 6 +Reference to value: 9 +nums is still usable: [3, 6, 9] +``` -The `sum()` method would've been nice earlier, huh πŸ˜‰. The `.sum()` method needs to have a type specified when using it. So we can specify the type when assigning the variable such as: +Once you have an iterator, a number of useful methods are available. Here is an +example of the sum method: -```rust +```{.rust filename="src/main.rs"} fn main() { let nums = vec![2, 4, 8]; let total: i32 = nums.iter().sum(); - println!("Sum is: {}", total); + println!("Sum is: {total}"); } ``` -## Enumerated iterators - -Often it is quite nice to have the index associated with each item in an iterator. To do so, we can use `.enumerate()` after calling either `.iter()` or `.into_iter()`. This modifies each element to be a tuple with `(i, xi)`. - -::: callout-note -The `.enumerate()` method returns the type `usize` which is a special type of unsigned integer. You can cast them to integers or floats using `i as i32`, for example. -::: +```default +Sum is: 14 +``` -Rust is 0 indexed so the first value of the enumeration is going to be `0`. +## Enumerating -For example: +You can also `.enumerate()` over iterators to get the index and value of the +current element. These are returned as a tuple `(i, xi)` where `i` is the index +and `xi` is the value. The data type of the index `i` is `usize`, which can if +needed be cast to `i32` with `i as i32`. Note that Rust is zero-indexed! -```rust +```{.rust filename="src/main.rs"} fn is_even(x: i32) -> bool { x % 2 == 0 } fn main() { - let x = vec![2, 4, 8]; + let x = vec![1, 3, 5]; for (i, xi) in x.iter().enumerate() { if is_even(i as i32) { - println!("i is even with value {xi}"); + println!("Index {i} is even with value {xi}"); } } } ``` +```default +Index 0 is even with value 1 +Index 2 is even with value 5 +``` -## Exercise 1 +## Mapping -- Modify the `mean()` function to calculate the mean using `.iter()` -- Create a vector of 5 or more `f64` values -- Calculate the mean and print the result +Maybe the most useful method to apply to iterators is `.map()`, the Rust analog +of `purrr::map()` and the `apply()` family of functions. Here is an example that +squares each element of a vector: -### Solution +```rust +// providing explicit type annotation in one +// value so the compiler knows its f64 and not f32 +let x = vec![5.9_f64, 6.8, 4.5, 7.3, 6.2]; -
-View solution +x.iter().map(|xi| xi.powi(2)) +``` + +Using `.map()` returns another iterator, so we can chain operations over +iterators by using multiple `.map()` statements. ```rust -fn mean(x: &[f64]) -> f64 { - let total: f64 = x.iter().sum(); - total / x.len() as f64 -} +// providing explicit type annotation in one +// value so the compiler knows its f64 and not f32 +let x = vec![5.9_f64, 6.8, 4.5, 7.3, 6.2]; -fn main() { - let nums = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - println!("Mean is: {}", mean(&nums)); -} +x.iter().map(|xi| xi.powi(2)).map(|xi| xi - 1.0) +``` + +## Closure + +In these examples we use a *closure* to modify each element of the iterator, +which is Rust's version of an anonymous function. The syntax of a closure is +`|arg| expression`, similar to R's recent adoption of `\(arg) expression`. Like +R's anonymous functions, they can also be multiple lines by wrapping the +expression in curly brackets: + +```rust +x.iter().map(|xi| { + let squared = xi.powi(2); + squared.sqrt() +}) ``` -
+When using `.enumerate().map()`, you get the Rust version of `purrr::imap()`. -## Exercise 2 +```rust +x.iter() + .enumerate() + .map(|(i, xi)| { + // use both i and xi here + }) +``` -- Print the index and value for each item in a vector for **only even** values +Notice that the iterator is now the tuple `(i, xi)` as before in the for-loop. -### Solution +## Collecting -
-View solution +In R, `purrr::map()` always returns a list, but in Rust, `.map()` always returns +another iterator, so getting back a concrete collection means you must `.collect()` +the values in the iterator. Rust cannot always infer the type you want to +collect, however, so you need to provide it explicitly: ```rust -fn main() { - let nums = vec![1, 2, 3, 4, 5, 6]; +let x_squared: Vec = x.iter().map(|xi| xi.powi(2)).collect(); +``` - for (i, n) in nums.iter().enumerate() { - if n % 2 == 0 { - println!("Index {i}: {n} is even"); - } - } -} +Alternatively, you can use the *turbofish* syntax `.collect::()` to +specify the type directly on `.collect()` rather than in the assignment: + +```rust +let x_squared = x.iter().map(|xi| xi.powi(2)).collect::>(); +``` + +In either case, you may use a general placeholders to encourage Rust to infer +the type, the syntax being `Vec<_>`: + +```rust +let x_squared: Vec<_> = x.iter().map(|xi| xi.powi(2)).collect(); +let x_squared = x.iter().map(|xi| xi.powi(2)).collect::>(); ``` -
+This lets Rust infer the element type while you specify the container type. \ No newline at end of file diff --git a/intro-rust/loops.qmd b/intro-rust/loops.qmd new file mode 100644 index 0000000..654e63d --- /dev/null +++ b/intro-rust/loops.qmd @@ -0,0 +1,81 @@ +--- +title: "Loops" +engine: knitr +--- + +In Rust, the easiest way to iterate over items in a collection is with a `for` +loop. The syntax should look familiar to R programmers. Each item in the +collection is bound to the name you provide before `in` at each iteration of the +loop: + +```rust +for value in collection { + // do something with value +} +``` + +As with control flow, the content of a for-loop must be delimited by curly +brackets, but the in-statement does not require parantheses. As an example, +consider a program for printing each number in a vector: + +```{.rust filename="src/main.rs"} +fn main() { + let nums = vec![1, 2, 3]; + + for n in nums { + println!("n is: {n}"); + } +} +``` + +Running `cargo run --quiet` (the `quiet` suppresses compiler logs) should print +the following to your terminal: + +```default +n is: 1 +n is: 2 +n is: 3 +``` + +In the context of for-loops, Rust's scoping rules are important to be aware of. +They are, in fact, much stricter than R's. Like R, values declared outside a +for-loop are accessible inside it, but unlike R, values created inside the loop +cannot be used outside it. In the following example, `greeting` is defined in +the outer scope and is accessible inside the loop: + +```{.rust filename="src/main.rs"} +fn main() { + let greeting = "Hi"; + let names = vec!["Alice", "Bob"]; + + for name in names { + println!("{greeting}, {name}!"); + } +} +``` + +```default +Hi, Alice! +Hi, Bob! +``` + +In contrast, `doubled` in the next example is defined inside the loop and cannot +be accessed after it ends: + +```{.rust filename="src/main.rs"} +fn main() { + let numbers = vec![1, 2, 3]; + + for n in numbers { + let doubled = n * 2; + println!("{n} doubled is {doubled}"); + } + + // ❌ `doubled` doesn't exist here + // println!("Last doubled: {}", doubled); +} +``` + +In the next section we will see how to get around this scoping rule using a +mutable variable declared in the outer scope to collect values produced inside +the loop. diff --git a/intro-rust/mutability.qmd b/intro-rust/mutability.qmd index 80f88a3..3c1121e 100644 --- a/intro-rust/mutability.qmd +++ b/intro-rust/mutability.qmd @@ -1,74 +1,56 @@ -# Mutability +--- +title: "Mutability" +engine: knitr +--- -::: callout-tip -Understand the difference between immutable and mutable variables. -::: - -In R everything is immutable (except environments). Immutable objects _cannot be altered_. - -::: aside - -Instead, when they are modified, they are copied. This is called copy-on-write (often referred to as **cow** semantics). - -::: - -In Rust, variables are immutable by default. This means once a value is assigned to a variable, it cannot be changed. To make a variable mutable, you must explicitly use the `mut` keyword. +In R, most objects are effectively immutable β€” when you modify one, R copies it +first. This is called copy-on-write semantics. Rust takes a stricter approach, +making variables immutable by default, and the compiler enforces this at compile +time. To make a variable mutable, you must declare it with the `mut` keyword: ```rust let mut x = 5; -x = 6; // βœ… works because x is mutable +x = 6; // works because x is mutable ``` -- Use `mut` when you need to change a variable after it is created. -- You do **not** re-bind the variable with `let` when changing its value. -- This helps catch bugs early by making data changes explicit. - +Without `mut`, attempting to reassign a variable is a compile error. This helps +catch bugs early by making data changes explicit. Note that when updating a +mutable variable, you do not re-bind it with `let` β€” you simply assign the new +value directly. -By requiring `mut`, the compiler ensures that accidental mutations are caught at compile time. +Mutability is especially useful for accumulating values across a loop. In the +previous section, we saw that variables defined inside a loop cannot be accessed +outside it. The solution is to declare a mutable variable in the outer scope and +update it inside the loop: -## Example - -Revisiting our loop from earlier: - -```rust +```{.rust filename="src/main.rs"} fn main() { - // create a vector let numbers = vec![1, 2, 3]; - // create a mutable value let mut doubled = 0; - // iterate through numbers to update doubled for n in numbers { doubled = n * 2; println!("{} doubled is {}", n, doubled); } - // βœ… compiles because `doubled` was declared - // _outside_ of the inner loop scope + // `doubled` is accessible here because it was declared outside the loop println!("Last doubled: {}", doubled); } ``` -## Exercise - -- Create a vector of 5 or more f64 values (`Vec`) -- Use a for loop to calculate the sum of the vector -- Calculate the mean from the sum of the vector and the length of the vector -- Print the result - -::: callout-tip -You can use `+=` shorthand to add and assign all at the same time. For example `x += 10` is equivalent to `x = x + 10`. -::: - -### Solution +```default +1 doubled is 2 +2 doubled is 4 +3 doubled is 6 +Last doubled: 6 +``` -
-View solution +You can also use `+=` to add and assign in one step β€” `x += 10` is equivalent to +`x = x + 10`. This makes computing running totals concise: -```rust +```{.rust filename="src/main.rs"} fn main() { let x = vec![1.0, 2.0, 3.0, 4.0]; - let n = x.len() as f64; let mut total = 0.0; @@ -79,4 +61,84 @@ fn main() { println!("The mean is: {}", total / n); } ``` -
+ +```default +The mean is: 2.5 +``` + +Like scalar variables, vectors must also be declared `mut` to modify them after +creation. You can create an empty mutable vector and let Rust infer the data +type based on how you go on to use it: + +```{.rust filename="src/main.rs"} +fn main() { + let mut names = Vec::new(); + names.push("Alice"); + names.push("Bob"); + println!("{:?}", names); +} +``` + +```default +["Alice", "Bob"] +``` + +Here, `Vec::new()` creates an empty vector, and `.push()` appends an element to +the end. Because the element appended is a character string, the Rust compiler +is able to infer the type of the names vector at compile time. With this +example, we have also introduced another method, namely `.push()`, which is +particularly useful, but there are, in fact, many ways to modify mutable vectors +in Rust. Let us mention just three here. + +**Clear** -- Instead of adding elements, you can remove all elements at once +with `.clear()`: + +```{.rust filename="src/main.rs"} +fn main() { + let mut nums = vec![1, 2, 3]; + nums.clear(); + println!("{:?}", nums); +} +``` + +```default +[] +``` + +**Sort** -- Vectors can be sorted in place with `.sort()`. Not all types support +ordering, but numeric types and strings do: + +```{.rust filename="src/main.rs"} +fn main() { + let mut x = vec![11, 3, 7, 10, 1]; + println!("x before sorting: {x:?}"); + x.sort(); + println!("x after sorting: {x:?}"); +} +``` + +```default +x before sorting: [11, 3, 7, 10, 1] +x after sorting: [1, 3, 7, 10, 11] +``` + +**Extend** -- To combine two vectors, use `.extend()`, which appends the +contents of one vector onto another: + +```{.rust filename="src/main.rs"} +fn main() { + let mut a = vec![1, 2]; + let b = vec![3, 4]; + a.extend(b); + println!("{:?}", a); +} +``` + +```default +[1, 2, 3, 4] +``` + +Note that the vector being extended must be `mut` and that the second vector is +moved into the first β€” it can no longer be used after the call. We will +elaborate on that last point more in the section on Rust's memory model, +specifically the concept of ownership. \ No newline at end of file diff --git a/intro-rust/mutable-vectors.qmd b/intro-rust/mutable-vectors.qmd deleted file mode 100644 index 6c3e363..0000000 --- a/intro-rust/mutable-vectors.qmd +++ /dev/null @@ -1,105 +0,0 @@ -# Mutable Vectors - -::: callout-tip -## Objective -Learn how to create and modify vectors with mutable operations. -::: - -In Rust, vectors (`Vec`) are growable arrays. To modify a vector after creating it, the vector itself must be declared as `mut`. - -::: callout-note -You will often see `` in Rust code or documentation. `T` is a way of saying "of any type." Seeing `Vec` can be read as "a vector of any type" or "generic over type `T`." -::: - -### Creating empty vectors - -You can create an empty vector and let Rust infer the type based on usage: - -```rust -fn main() { - let mut names = Vec::new(); - names.push("Alice"); - names.push("Bob"); - println!("{:?}", names); -} -``` - -- `Vec::new()` creates an empty vector. -- `.push()` adds an element to the end of a vector. - -### Clearing vectors - -You can remove all elements from a vector using `.clear()`: - -```rust -fn main() { - let mut nums = vec![1, 2, 3]; - nums.clear(); - println!("{:?}", nums); // prints [] -} -``` - -### Sorting vectors - -Use `.sort()` to sort a vector. Important to note that not all types can be sorted. - -```rust -fn main() { - let mut x = vec![11, 3, 7, 10, 1]; - println!("x before sorting: {x:?}"); - x.sort(); - println!("x after sorting: {x:?}"); -} -``` -``` -x before sorting: [11, 3, 7, 10, 1] -x after sorting: [1, 3, 7, 10, 11] -``` - -### Combining vectors - -Use `.extend()` to append one vector's contents to another: - -```rust -fn main() { - let mut a = vec![1, 2]; - let b = vec![3, 4]; - // note that we don't assign. - // Instead, `a` is modified in place - a.extend(b); - println!("{:?}", a); // prints [1, 2, 3, 4] -} -``` - -- `.extend()` adds the contents of another vector. -- The original vector must be `mut`. -- The second vector is **moved** into the first. -- `b` can no longer be used. - -## Exercise - -- Create an empty vector -- Append the values `1.0`, `2.0`, and `3.0` using `.push()` -- Clear the vector to make it empty -- Lastly, extend it with another vector e.g. `[4.0, 5.0]` -- Sort the vector -- Print the final result. - -### Solution - -
-View solution - -```rust -fn main() { - let mut x = Vec::new(); - x.push(1.0); - x.push(2.0); - x.push(3.0); - x.clear(); - x.extend(vec![4.0, 5.0]); - println!("{:?}", x); -} -``` - -
diff --git a/intro-rust/options.qmd b/intro-rust/options.qmd deleted file mode 100644 index 56975ca..0000000 --- a/intro-rust/options.qmd +++ /dev/null @@ -1,83 +0,0 @@ -# `Some()` or `None`? - -::: callout-tip -## Objective - -Understand how missingness is handled in Rust with `Option`. -- Understand how to handle missingness -- Use `Option` in an applied case -::: - -Rust doesn't have the concept of a `NULL` type. Null values are quite dangerous and have their own category of errors and security issues. - -Rust makes it impossible to have a null value by formalizing the concept of missingness through the `Option<>` - -## `Option` - -An Option is a special `enum` that has two possible values: - -```rust -enum Option { - Some(T), - None -} -``` - -When a value is missing, the option's variant is `None`. When the option contains _some value_, the value is contained _inside of_ the `Some(T)` variant. - - -## Accessing `Some()` value - -Since `Option` are "just" an enum, we can match on the variants to access the values. - -```rust -// create an Option that contains a value -let measure = Some(Measure::Euclidean); - -match measure { - Some(v) => println!("The measure is not missing!"), - None => println!("Oh no! The measure is missing"), -} -``` - -We put a placeholder value inside of `Some(v)` to create a binding to the inner value. It's like a function argument, it can be called whatever you want it to be. - -## Danger! `.unwrap()` & `.expect()` - -Sometimes dealing with options is a headache, particularly when we're in the early stages of developing. - -We can use `.unwrap()` or `.expect()` to grab the inner value of an option _without matching_. - -This is **dangerous!!** because we are ignoring the possibility of a `None`. Unwrapping on a `None` leads to a **panic**. Panics cause your program to abort. - -```rust -thread 'main' panicked at src/main.rs:4:41: -called `Option::unwrap()` on a `None` value -``` - - -## Exercise - -- Modify the `distance()` method to take an `Option<&Measure>` -- When `None` use euclidean distance, otherwise use the provided distance measure - -::: aside -The exercise does not involve `.unwrap()` because it's a bad habbit. Let's try and form good habbits from the outset if possible. -::: - -
-View solution -```rust -impl Point { - fn distance(&self, destination: &Self, measure: Option<&Measure>) -> f64 { - match measure { - Some(m) => match m { - Measure::Euclidean => self.euclidean_distance(destination), - Measure::Haversine => self.haversine_distance(destination), - }, - None => self.euclidean_distance(destination), - } - } -} -``` -
diff --git a/intro-rust/ownership.qmd b/intro-rust/ownership.qmd new file mode 100644 index 0000000..7ce1ace --- /dev/null +++ b/intro-rust/ownership.qmd @@ -0,0 +1,199 @@ +--- +title: "Ownership" +engine: knitr +--- + +Rust is notorious for its **borrow checker**, the mechanism that enforces memory +safety at compile time. The core rule is that every value has a single owner, +and once that ownership is transferred, the original variable can no longer be +used. This is called a **move**. A move occurs whenever a variable is passed to +a function or assigned to another variable. After the move, the original binding +is invalid. Consider this example from The Book: + +```{.rust filename="src/main.rs"} +fn main() { + let s1 = String::from("hello"); + let s2 = s1; + println!("{s1}, world!"); +} +``` + +Running this code will cause the following error: + +```default +error[E0382]: borrow of moved value: `s1` + --> src/main.rs:4:16 + | +2 | let s1 = String::from("hello"); + | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait +3 | let s2 = s1; + | -- value moved here +4 | println!("{s1}, world!"); + | ^^ value borrowed here after move + | + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider cloning the value if the performance cost is acceptable + | +3 | let s2 = s1.clone(); + | ++++++++ + + +For more information about this error, try `rustc --explain E0382`. +``` + +There is a lot of output here, but let us focus on the source of the error for +now. First, we see that `let s2 = s1;` moves the value in `s1` to `s2`, and then +we are told that the `println!` macro mistakenly seeks to borrow the value that +has been moved. Since that value is no longer *owned* by `s1`, it causes an +error. + +## Cloning + +In the example above, we are also given helpful advice about how to fix the +error using `.clone()`, which copies the value rather than moving it. Cloning is +probably the simplest way to reuse the original value, as almost everything in +Rust can be cloned. + +```{.rust filename="src/main.rs"} +fn main() { + let s1 = String::from("hello"); + let s2 = s1.clone(); + println!("{s1}, world!"); +} +``` + +```default +hello, world! +``` + +## Functions + +The above example also highlights that the `println` macro seeks to *borrow* the +value in `s1`. In fact, any time you pass a variable to a function, its value is +moved, meaning the originally defined variable can no longer be used to access +the value outside of the function. For example, let's define a `mean()` +function: + +```rust +fn mean(x: Vec) -> f64 { + let mut total = 0.0; + let n = x.len(); + + for xi in x { + total += xi; + } + + total / (n as f64) +} +``` + +Calling this function twice on the same vector will fail because the first call +moves `x` into the function: + +```rust +let x = vec![0.0, 3.14, 10.1, 44.8]; + +let avg1 = mean(x); // ⬅️ x moved here! +let avg2 = mean(x); // ❌ compiler error +``` + +As we just saw, one way of handling this is to clone the data before passing it +to the function. + +```rust +let x = vec![0.0, 3.14, 10.1, 44.8]; + +let avg1 = mean(x.clone()); // ⬅️ x cloned here! +let avg2 = mean(x); // βœ… compiler happy +``` + +While cloning in this way is safe and correct, it has the unfortunate downside +of allocating a new copy of the data each time β€” not an issue for small vectors +like `x`, but as they grow, finding a better approach will become more urgent. + +## Borrowing + +Fortunately, there is a more efficient approach known as *borrowing*, which +involves passing a reference to a value rather than moving it. In Rust, we +signal a reference by placing an ampersand `&` in front of a variable or type +name, for example, `&x` or `&Vec`. These tell the compiler that we are +borrowing a value rather than taking ownership. To use the `mean()`function with +a reference, notice that we have to update its signature to make the reference +type explicit: + +```rust +fn mean(x: &Vec) -> f64 { + let mut total = 0.0; + let n = x.len(); + + for xi in x { + total += *xi; + } + + total / (n as f64) +} + +fn main() { + let x = vec![1.0, 2.0, 3.0]; + let avg = mean(&x); // πŸ‘ˆ borrowing `x` + println!("x is still usable: {:?}", x); +} +``` + +```default +x is still usable: [1.0, 2.0, 3.0] +``` + +An important restriction on referencing is that borrowed values cannot be moved +or mutated β€” only read. You can borrow a value as many times as you like, you +just cannot change it. + +## Slices + +Rather than reference the entire array or vector, we can reference a slice, or a +contiguous sequence of elements within it. These are denoted in Rust using `&[T]` +where `T` stands for a generic type. For example, `&[f64]` means a reference +slice of 64-bit floating points. If we want to demonstrate the use of reference +slices with our `mean()` function, we will as before have to update its +signature to make that type explicit: + +```{.rust filename="src/main.rs"} +fn mean(x: &[f64]) -> f64 { + let mut total = 0.0; + let n = x.len(); + + for xi in x { + total += *xi; + } + + total / (n as f64) +} + +fn main() { + let x = [0.0, 20.0, 742.3]; // array + let y = vec![1.0, 2.0, 3.0]; // vector + + println!("the mean of x is: {}", mean(&x)); + println!("the mean of y is: {}", mean(&y)); +} +``` + +```default +the mean of x is: 254.1 +the mean of y is: 2 +``` + +There are two things to note about this example. The first is very subtle. We +updated the signature for mean to take `x: &[f64]`, a slice. However, we passed +`&x` and `&y` to the function, which are references to arrays and vectors. What +is going on here? The answer is an implicit conversion. Both arrays and vectors +have methods for converting their generic references to slices, and those are +invoked when passing them into a function with this signature. + +Following from that, the second thing to note is that a function written to +accept `&Vec` works only on vectors. That is because there is no method +for converting array references to vector references. But as we just saw, both +can be converted to slices. Rewriting a function to accept `&[f64]`, thus, makes +it more general at no cost. So, in practice, we should prefer slices over vector +references whenever the function only needs to read the data β€” it is more +flexible and equally efficient. diff --git a/intro-rust/parallelize.qmd b/intro-rust/parallelize.qmd deleted file mode 100644 index c2759cb..0000000 --- a/intro-rust/parallelize.qmd +++ /dev/null @@ -1,15 +0,0 @@ -# Parallelizing - -TODO: before this we do combining iterators with .zip() and then go over destructuring into tuples there - -- this is a stretch exercise - -- Brief intro to rayon -- `par_iter()` -- `into_par_iter()` -- `with_min_len()` - - -- Objective is to calculate the pairwise distances between two vectors of points super fast -- we want to use `.zip()` to combine the two vectors -- this would require using rand to create a vector of random numbers diff --git a/intro-rust/references-slices.qmd b/intro-rust/references-slices.qmd deleted file mode 100644 index 5f14b25..0000000 --- a/intro-rust/references-slices.qmd +++ /dev/null @@ -1,190 +0,0 @@ -# Ownership - -::: callout-tip -## Objective - -Understand the how Rust ensures memory safety through the borrow checker and be able to avoid issues by borrowing, using a slice, or cloning. -::: - - -Rust is notorious for its **borrow checker**. The borrow check is Rust's secret sauce which helps ensure memory safety. At compile time, Rust looks through all of your code to enforce **ownership**. The key is that - -> ### variables can be used only once! - -If a variable has been used it has been **moved**. There are a number of ways to reuse variables. - -## Moves - -A move occurs whenever a variable is used by a function or a method. - -```rust -fn main() { - let x = vec![1.0, 2.0, 3.0]; - let y = x; // ⬅️ ownership moved - println!("{:?}", x); // ❌ error: value used after move -} -``` - -Since `x` was moved (used) by assigning it to `y`, the original value cannot be used. - - -## Cloning - -The simplest but least efficient way to reuse a variable is to clone it. This is what R does. - -::: callout-tip -Remember: when in doubt, clone it out! -::: - -Almost everything in Rust can be cloned by using the `.clone()` method. - -::: callout-important - -#### Cannot compile - -```rust -let x = v![0.0, 3.14, 10.1, 44.8]; - -let avg1 = mean(x); // ⬅️ x moved here! -let avg2 = mean(x); // ❌ compiler error -``` -::: - -If we first clone `x` before using `mean()` we can use `x` again. - -```rust -let x = v![0.0, 3.14, 10.1, 44.8]; - -let avg1 = mean(x.clone()); // ⬅️ x cloned here! -let avg2 = mean(x); // βœ… compiler happy -``` - - -## Borrowing - -While variables may only be used once, they can be **borrowed** infinitely (until they go out of scope). A variable can be used by **reference** when the `&` symbol is placed in front of itβ€”e.g. `arg: &Vec`. - -```rust -fn main() { - let x = vec![1.0, 2.0, 3.0]; - let avg = mean(&x); // πŸ‘ˆ borrowing `x` - println!("x is still usable: {:?}", x); -} -``` - -If you borrow a variable you cannot mutate it or move it. - -## Slices - -Slices are a special type of borrowing. Slices are a reference to contiguous section of the same type. They're recognized by the syntax `&[T]`. - -Slices always have a known length (accessed via `.len()`) and can be used from more than one type. - -### Example - -Both an array `[f64; N]` and a `Vec` can be turned into a `&[f64]` - -```rust -let x = [0.0, 20.0, 742.3]; -let y = vec![1.0, 2.0, 3.0]; - -let avg_x = mean(&x) -let avg_y = mean(&y); -``` - -Slices are more flexible and more light-weight than borrowing a full vector and should be preferred whenever possible. - -In general: slice > reference > owned - - - - -## Exercise 1 - -- Calculate the mean of the same `Vec` twice -- Print the vector at the end - -
-View solution - -```rust -fn mean(x: Vec) -> f64 { - let mut total = 0.0; - for xi in x { - total += xi; - } - total / x.len() as f64 -} - -fn main() { - let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let result = mean(x.clone()); - let result2 = mean(x.clone()) - println!("The vector is still {:?}", x); -} -``` - -
- -## Exercise 2 - -- Rewrite the `mean()` function to accept a **reference** to a `Vec` instead of taking ownership -- Then call it with a borrowed vector. - - -
-View solution - -```rust -fn mean(x: &Vec) -> f64 { - let mut total = 0.0; - for xi in x { - total += xi; - } - total / x.len() as f64 -} - -fn main() { - let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let result = mean(&x); - println!("The mean of x is {result}"); -} -``` - -
- - -## Exercise 3 - -- Rewrite `mean()` to accept a slice (`&[f64]`) instead of a `Vec` reference. -- Create an array of `f64` values called `y` -- Calculate the mean on both `x` and `y` -- Print both averages - - -
-View solution - -```rust -fn mean(x: &[f64]) -> f64 { - let mut total = 0.0; - for xi in x { - total += xi; - } - total / x.len() as f64 -} - -fn main() { - let x = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let y = [0.0, 9.5, 3.3, 11.78, 3.14159]; - println!("The mean of x is {}.\nThe mean of y is {}", mean(&x), mean(&y)); -} -``` - -
diff --git a/intro-rust/struct-methods.qmd b/intro-rust/struct-methods.qmd deleted file mode 100644 index d5ebe1e..0000000 --- a/intro-rust/struct-methods.qmd +++ /dev/null @@ -1,193 +0,0 @@ -# Struct Methods - -::: callout-tip -## Objectives - -Learn how to implement methods for stucts. Be able to create associated functions and understand the difference between `Self`, `&self`, and `&mut self` - - -::: - -## Associated Functions - -Typically methods are used on instantiations of a variable such as: - -```rust -let me = Person { name: "Josiah".to_string(), age: 29 }; -let birth_year = me.year_of_birth(); -``` - -Sometimes it makes sense to use an **associated function**. These are functions that are called from the type itself e.g. `Type::function()`. They're typically used for *constructors*. For example - -```rust -let me = Person::new("Josiah".to_string(), 29); -``` - -They're created using an `impl` block. - -## `impl`ement methods - -`impl` is short for **implement**. We can implement methods for structs using the following syntax: - -```rust -impl TypeName { - fn method() { - todo!() - } -} -``` - -Associated functions are methods that do not reference the type itself. To create a `new()` constructor function that returns the type we can set the return type to `Self`: - -```rust -impl Person { - fn new(name: String, age: i32) -> Self { - Person { name, age } - } -} -``` - -This gives us the ability to use `Person::new()` - -## Self references - -However, it often makes sense to call methods _from_ an instantiation itself. - -We can do this by setting the first argument to `&self` which provides a **reference** to the struct the method is called on. - -```rust -impl Person { - fn greet(&self) { - println!("Hello, my name is {} and I am {} years old.", self.name, self.age); - } -} -``` - -Using a `&self` reference means you cannot _move_ the inner fields, only borrow them. - - -We can also have arguments that refer to other instantiations of the same type via `&Self` or `Self`. For example to compare ages of people: - -```rust -impl Person { - fn is_older_than(&self, other_person: &Self) -> bool { - self.age > other_person.age - } -} -``` - -## Mutable self - -Often it makes sense to modify the fields of a struct. This is when a _mutable reference_ is handy via `&mut self`. - -For example we can implement methods to `rename()` and `celebrate_birthday()`: - -```rust -impl Person { - fn rename(&mut self, new_name: &str) { - self.name = new_name.to_string(); - } - - fn celebrate_birthday(&mut self) { - self.age += 1; - } -} -``` - -## Section 2 - -## Another Section - -## Exercise - -- Implement a `Point::new()` associated function - - Using arguments `x: f64` and `y: f64` - - It should return `Self` -- Define a method `euclidean_distance()` which calculates the distance between itself and another `Point` - - Use arguments `&self` and `destination: &Self` - - Return `f64` - - Distance is calculated as $\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$ - - Use `.powi(2)` to square a value and `x.sqrt()` to get the square root -- In `main.rs` create two `Point` structs and calculate the distance between them. - - -
- View solution - -```rust -impl Point { - fn new(x: f64, y: f64) -> Self { - Self { x, y } - } - - fn euclidean_distance(&self, destination: &Self) -> f64 { - let x_diff = (self.x - destination.x).powi(2); - let y_diff = (self.y - destination.y).powi(2); - - (x_diff + y_diff).sqrt() - } -} - -fn main() { - let a = Point::new(0.0, 5.0); - let b = Point::new(-10.0, 1.5); - let distance = a.euclidean_distance(&b); - println!("Distance between a and b is {distance}"); -} -``` -
- - - -## Bonus: Exercise 2 - - -Euclidean distance is useful only on a rectangle. What about on a sphere? Haversine distance is used to calculate the distance on a sphere. The Haversine formula computes the distance between two points on a sphere given their longitudes and latitudes. - -It is defined by the following (messy) equation: - - -$$a = \sin^2\left(\frac{\Delta\phi}{2}\right) + \cos\phi_1 \cdot \cos\phi_2 \cdot \sin^2\left(\frac{\Delta\lambda}{2}\right)$$ - -$$c = 2 \cdot \text{atan2}(\sqrt{a}, \sqrt{1-a})$$ - -$$d = R \cdot c$$ - -Where: - -- $\phi$ represents latitude (in radians) (use `.to_radians()`) -- $\lambda$ represents longitude (in radians) (use `.to_radians()`) -- $R$ is the Earth's radius (you can use `6_371_008_7714f64` meters as a mean radius) -- $\Delta\phi$ is the difference in latitude between the two points ($\phi_2 - \phi_1$) -- $\Delta\lambda$ is the difference in longitude between the two points ($\lambda_2 - \lambda_1$) -* $\text{atan2}(y, x)$ is the **arctangent** of $y/x$, using the signs of both arguments to determine the correct quadrant. - - -**Hints for Implementation:** - -* Remember to convert your latitude and longitude values from degrees to **radians** using the `.to_radians()` method on `f64`. -* The `c` part of the formula, $2 \cdot \text{atan2}(\sqrt{a}, \sqrt{1-a})$, can also be implemented using `2 * a.sqrt().asin()` in Rust, which is a common simplification when `a` is within the valid domain for `asin`. - - -
- View solution - -```rust -impl Point { - fn haversine_distance(&self, destination: &Self) -> f64 { - let radius = 6_371_008.7714; // Earth's mean radius in meters - let theta1 = self.y.to_radians(); // Latitude of point 1 - let theta2 = destination.y.to_radians(); // Latitude of point 2 - let delta_theta = (destination.y - self.y).to_radians(); // Delta Latitude - let delta_lambda = (destination.x - self.x).to_radians(); // Delta Longitude - - let a = (delta_theta / 2f64).sin().powi(2) - + theta1.cos() * theta2.cos() * (delta_lambda / 2f64).sin().powi(2); - - 2f64 * a.sqrt().asin() * radius - } -} - -``` - -
diff --git a/intro-rust/structs.qmd b/intro-rust/structs.qmd deleted file mode 100644 index b73940f..0000000 --- a/intro-rust/structs.qmd +++ /dev/null @@ -1,145 +0,0 @@ -# Defining Struct(ure)s - -::: callout-tip -## Objective - -Create new `struct` types and destructure them. Additionally, add behavior through `derive`-macros. -::: - -You can define a type as being a collection of other types by using the `struct` keyword. - - -```rust -struct Person { - name: String, - age: i32 -} -``` - -`struct`s are named using PascalCase, as opposed to `fn`s which are named using snake_case convention. Structs are constructed using a "literal" syntax. Where we write the name of the struct followed by curlys and the fields like so: - -```rust -let me = Person { name: "Josiah".to_string(), age: 29 }; -``` - -By default, new structs do not get any special behavior. Meaning they cannot be printed using `println!()`: - -```rust -struct Person { - name: String, - age: i32 -} - -fn main() { - let me = Person { name: "Josiah".to_string(), age: 29 }; - println!("{me:?}"); -} -``` -``` -error[E0277]: `Person` doesn't implement `Debug` - --> src/main.rs:8:15 - | -8 | println!("{me:?}"); - | ^^^^^^ `Person` cannot be formatted using `{:?}` - | - = help: the trait `Debug` is not implemented for `Person` - = note: add `#[derive(Debug)]` to `Person` or manually `impl Debug for Person` - = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider annotating `Person` with `#[derive(Debug)]` - | -1 + #[derive(Debug)] -2 | struct Person { -``` - -## Deriving behavior - -Rust contains a special type called a **trait**. They're akin to S3 generics. They are a a collection of methods that can be implemented by foreign types. - -In this case, `Debug` is a trait that allows for debug printing using `println!()`. Our new type does not implement this by default. However, we can **derive** this behavior because everything inside of the struct _does_. - -To derive behavior we use the `#[derive()]` **attribute**. - -```rust -#[derive(Debug, Clone)] -struct Person { - name: String, - age: i32 -} -``` - -The `Debug` trait allows debug printing of the type `Person` via `dbg!()` or `println!("{p:?})`. - -```rust -fn main() { - let me = Person { name: "Josiah".to_string(), age: 29 }; - println!("{me:?}"); -} -``` -``` -Person { name: "Josiah", age: 29 } -``` - -## Accessing Fields - -Fields of a `struct` can be accessed directly, or by reference. Field can be accessed using `var.field_name`. - -If a field is accessed and moved, the `struct` cannot be moved. The same rules of borrowing apply to a struct. You can borrow a field as much as you want but you can only move it once. Otherwise, the entire struct cannot be used. - -```rust -fn main() { - let me = Person { name: "Josiah".to_string(), age: 29 }; - let name = me.name; - println!("{me:?}"); -} -``` -```rust -error[E0382]: borrow of partially moved value: `me` - --> src/main.rs:10:15 - | -9 | let name = me.name; - | ------- value partially moved here -10 | println!("{me:?}"); - | ^^^^^^ value borrowed here after partial move -``` - - -## Destructuring - -It is possible to **destructure** a struct during assignment creating many variables at one time. - -To destructure during assignment you use the syntax `let StructName { field, field } = variable;` - -```rust -fn main() { - let me = Person { name: "Josiah".to_string(), age: 29 }; - let Person { name, age } = me; - println!("{name} is {age} years old"); -} -``` - -## Exercise - -- Define a struct called `Point` which has two fields `x`, and `y` that are `f64` -- Derive `Debug` and `Clone` -- In `main()` create a new `Point` struct -- Debug print the new point -- Destructure the point into x and y - - -
- View solution - -```rust -#[derive(Debug, Clone)] -struct Point { - x: f64, - y: f64 -} - -fn main() { - let point = Point { x: 3.0, y: 0.14 }; - println!("The point is {:?}"); - let Point { x, y } = point; -} -``` -
diff --git a/intro-rust/structures.qmd b/intro-rust/structures.qmd new file mode 100644 index 0000000..23ab6c0 --- /dev/null +++ b/intro-rust/structures.qmd @@ -0,0 +1,168 @@ +--- +title: "Structures" +engine: knitr +--- + +Rust lets you define custom types by grouping related values together using the +`struct` keyword. This is similar in spirit to a named list in R, but with fixed +fields and a declared type. Struct names use PascalCase, while field names use +snake_case: + +```rust +struct Person { + name: String, + age: i32, +} +``` + +A struct is created using "literal" syntax, where we write the struct name +followed by curly brackets containing each field and its value: + +```rust +let me = Person { name: "Josiah".to_string(), age: 29 }; +``` + +## Derive + +By default, a new struct has no built-in behavior. It does not even have a +defined behavior for printing. So, right now, if you try to print the `Person` +struct, you will get an error. + +```{.rust filename="src/main.rs"} +struct Person { + name: String, + age: i32 +} + +fn main() { + let me = Person { name: "Josiah".to_string(), age: 29 }; + println!("{me:?}"); +} +``` + +```default +error[E0277]: `Person` doesn't implement `Debug` + --> src/main.rs:8:15 + | +8 | println!("{me:?}"); + | ^^^^^^ `Person` cannot be formatted using `{:?}` because it doesn't implement `Debug` + | + = help: the trait `Debug` is not implemented for `Person` + = note: add `#[derive(Debug)]` to `Person` or manually `impl Debug for Person` + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `Person` with `#[derive(Debug)]` + | +1 + #[derive(Debug)] +2 | struct Person { + | + +For more information about this error, try `rustc --explain E0277`. +``` + +Here again we see a great demonstration of Rust's very informative error +messages. In this case, it also suggests a solution. We should derive the +`Debug` trait. What is a trait exactly? In Rust, traits are similar to R's S3 +generics β€” they define a set of methods that a type can support. In this case, +the `Debug` trait allows for printing the content of a struct to stdout using +the special `:?` syntax in `println!()` or the `dbg!()` macro. + +For very simple behaviors like printing, it is possible to derive the behavior +automatically from the struct definition itself. In fact, this is what the error +message above is telling us to do. It tells us to use another type of macro, +specifically the `derive` macro, placing it above the struct definition and +telling it what specific behavior to derive, in this case, `Debug`. + +```{.rust filename="src/main.rs"} +#[derive(Debug)] +struct Person { + name: String, + age: i32 +} + +fn main() { + let me = Person { name: "Josiah".to_string(), age: 29 }; + println!("{me:?}"); +} +``` + +```default +Person { name: "Josiah", age: 29 } +``` + +## Fields + +Fields in a struct can be accessed with dot notation: `me.name`, `me.age`. But +beware that ownership rules apply here, too. Accessing a field by moving it out +of the struct partially moves the struct itself, making the whole struct +unusable afterward: + +```{.rust filename="src/main.rs"} +#[derive(Debug)] +struct Person { + name: String, + age: i32, +} + +fn main() { + let me = Person { name: "Josiah".to_string(), age: 29 }; + let name = me.name; // ⬅️ name moved out + println!("{me:?}"); // ❌ error: partial move +} +``` + +```default +error[E0382]: borrow of partially moved value: `me` + --> src/main.rs:10:16 + | + 9 | let name = me.name; // name moved out + | ------- value partially moved here +10 | println!("{me:?}"); // error: partial move + | ^^ value borrowed here after partial move + | + = note: partial move occurs because `me.name` has type `String`, which does not implement the `Copy` trait + = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0382`. +``` + +To read a field without consuming it, borrow it instead by referencing the +struct. + +```{.rust filename="src/main.rs"} +#[derive(Debug)] +struct Person { + name: String, + age: i32, +} + +fn main() { + let me = Person { name: "Josiah".to_string(), age: 29 }; + let name = &me.name; + println!("{me:?}"); +} +``` + +```default +Person { name: "Josiah", age: 29 } +``` + +## Destructuring + +Rust lets you unpack all fields of a struct in a single `let` binding. This is +called destructuring and uses the syntax `let StructName { field1, field2 } = variable`: + +```{.rust filename="src/main.rs"} +fn main() { + let me = Person { name: "Josiah".to_string(), age: 29 }; + let Person { name, age } = me; + println!("{name} is {age} years old"); +} +``` + +```default +Josiah is 29 years old +``` + +Destructuring is useful when you need several fields at once and want to avoid +repeated `.field` access. The struct is consumed in the process, so the original +variable is no longer available. diff --git a/intro-rust/types.qmd b/intro-rust/types.qmd index 12b015e..475f15e 100644 --- a/intro-rust/types.qmd +++ b/intro-rust/types.qmd @@ -1,114 +1,81 @@ -# Basic Types - -::: callout-tip -## Objective - -Familiarize yourself with basic types in Rust and when they may be used. - -::: - -In R everything is a vector. A vector is a **collection** of values. There is no **scalar** type for a double/ character / integer / logical vector. Those are just length 1 vectors. - -In Rust, however, scalars are the building blocks of everything. When a collection is needed those are made directly from scalars. You will often hear of these as **primitives**. - -Today we will only be using a handful of these primitives but it is important to understand what they areβ€”in general. - - - -## Integers - -Integers are either **signed** or **unsigned**. - -A signed integer can contain a _negative_ value. Whereas an unsigned integer can contain only positive numbers. - - -- Signed Integers: `i8`, `i16`, `i32`, `i64`, `i128` -- Unsigned Integers: `u8`, `u16`, `u32`, `u64`, `u128` - - -::: callout-note -In R, an integer vector is comprised of `i32` values. -::: - -The letter prefix is the type of primitive. The following number indicates how many bits can be used to store the values. Unsigned integers can contain more possible values because they do not have to support negative numbers. - -```rust +--- +title: "Basic Types" +engine: knitr +--- + +In R, everything is a vector or a *collection* of values. There is no concept of +a single value variable or scalar. The closest equivalent to that is a +length-one vector. In Rust, however, scalars are the building blocks of +everything. When a collection is needed, it is made directly from scalars. For +this reason, you will often hear them referred to as **primitives**. A +primitive or scalar can be used to hold a single value of a specific data type, +including strings, integers, and floats, among others. In this tutorial, we will +cover some of those data types, focusing on how to instantiate them and convert +between them. + +Integers in Rust are a good place to start. They can be either signed, meaning +they can represent negative values, or unsigned, meaning they support only +positive values. Both varieties come in several sizes depending on how many bits +are needed to store the value: + +- Signed integers: `i8`, `i16`, `i32`, `i64`, `i128` +- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128` + +The letter prefix indicates the type (`i` for signed, `u` for unsigned) and the +number indicates the bit width. Unsigned integers can hold larger positive +values than their signed counterparts because they do not need to use a bit to +represent a negative value: + +``` rust i32::MAX // 2147483647 u32::MAX // 4294967295 ``` -## Floating point - -In many cases where we need to do math we will use a floating point number. Floating points are **signed** and allow for decimal values. - -There are two types: `f32` and `f64`. +Floating points are similar to integers but can represent decimal values, too. +Unlike integers, floating points are also always signed and come in two sizes: +`f32` and `f64`. -::: callout-note -In R, a double vector is comprised of `f64` values. +::: {.callout-note} +In R, integer vectors are comprised of only `i32` values, and floating points, +called doubles, are always `f64` values. ::: -## Strong typing +Rust can usually infer types. For instance, it can distinguish the integer `1` +from the floating point `1.0`, but you can also specify the type explicitly. +There are two ways to do this β€” using `:` in the assignment, or by appending a +type suffix to the literal value: +``` rust +let x: f64 = 10.0; +let x = 10.0f64; +``` -Rust will infer a value's type exceptionally well! However, we may want to specify the type manually as well. We can do this 2 primary ways: - -- In assignment using `:` for example `let x: f64 = 10;` -- Or by specifying the suffix e.g. `10f64` or `10_i32` - -::: callout-tip -You can use `_` as a visual separator when specifying numbers in Rust. The following are all identical values: +You can also use `_` as a visual separator when writing numeric literals. The +following are all identical: -```rust +``` rust let x: i32 = 1000; let x = 1000i32; let x = 1_000_i32; ``` -::: - -## Type casting - -In Rust, math expressions can only be performed between like-types. You **cannot** add `2.0 + 3_f64`. To accomplish this we must **cast** to the same types. -Primitive types can be cast into one another using the `as` keyword. To add an `f64` to an `i32` we should ensure they're the same type. - -```rust +This pattern of explicitly declaring the type of a variable may seem somewhat +verbose to the average R user since R is very lax about types, relying as it +does on a lot of implicit conversions. Becuase of Rust's memory management +model, however, Rust is a strongly, statically typed language. That means that +all types must be known when you go to build or compile your crate. It also +precludes running operations on different types. For example, math expressions +can only be performed between values of the same type, so you cannot, for +example, directly add an `f64` and an `i32`. You must first cast one to match +the type of the other, in the simplest case using the `as` keyword: + +``` rust fn add2(x: f64, y: i32) -> f64 { - x + y as f64 -} -``` - -## Unit type - -In Rust there is no concept of `NULL`. Instead there is the **unit type** which is represented as `()`. - -::: aside -The unit type is _technically_ a tuple without any fields. If the internet is to be believed it is called "unit" becaue it can only have one value and is related to the "singleton" or unit set in set theory. -::: - -Functions that do not return anything _technically_ return `()`. - -## Exercise - -Modify `src/main.rs` to add two or more incompatible types. - -- Define a variable `x` to be an `f64` value -- Define `y` to be an `i32` value -- Add them up and store them in the variable `z` -- Use `println!()` to print the value of `z` - - -### Solution - -
- View solution - -```rust -fn main() { - let x = 3.14; - let y = 47_i32; - let z = x + y as f64; - println!(" The value of z is {z}"); + x + (y as f64) } ``` -
+If you call `add2(3.2, 2)`, the function will first cast `y` to `f64` before +doing addition, returning the `f64` value `5.2`. Before we continue, note that +Rust supports all the same numeric operators as R: `+`, `-`, `/`, `*`, and `%` +for remainder (equivalent to `%%` in R). \ No newline at end of file diff --git a/intro-rust/why-rust.qmd b/intro-rust/why-rust.qmd deleted file mode 100644 index bcdd03f..0000000 --- a/intro-rust/why-rust.qmd +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Why Rust? ---- - -::: callout-tip -## Objective - -Provide motivation for _why_ learning Rust is a use of our time. Discuss _what_ Rust is and how it is unique compared to other compiled langauges. - -🧠 Think of Rust as "C for people who like error messages that actually help." -::: - -## Reasoning - -Rust is a programming language that’s fast, safe, and surprisingly friendly to use. Rust turns your code into a standalone program that runs directly on your computer. This makes it much faster and more efficient, similar to languages like C or C++. But where those languages can be hard to use and easy to break, Rust was built to be safer and more helpful. - -Rust is especially good at preventing bugs related to memory. There's no garbage collector in Rust so it is quite memory efficient. Moreover, Rust is designed with the developer in mind. The rust compiler provides error messages that rivalβ€”or maybe even surpassβ€”the quality of tidyverse error messaging. - - -## TL;DR - -- R is interpreted. Code runs line by line. -- R (and Python) are built on C. -- C is compiled β€” it builds a binary that runs directly on your machine. -- Rust is also compiled, like C, C++, Go, Java, and Fortran. -- These languages are "close to the metal" β€” fast, efficient, and powerful. -- Rust matches C++ in speed, but with some key advantages: - - **Memory safety** without a garbage collector (unlike Go), thanks to the borrow checker. - - A great developer experience: - - Helpful, friendly compiler errors (Tidyverse-level DX). - - Modern tooling (cargo, rustdoc, rustfmt, *rust-analyzer*, etc.). -- Rust is easy to get started with and rewards best practices. - - We won’t go deep into memory safety or concurrency today -- Rust uses errors as data, unlike R and C++, Rust does not support exceptions From a96496adfbd39b1d73b2e189268ab678dc1e009c Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:15:06 -0600 Subject: [PATCH 09/20] prepare user guide for new versions --- .../basic-error-handling.qmd | 0 .../{type-mapping => _drafts}/characters.qmd | 0 .../{type-mapping => _drafts}/collections.qmd | 0 user-guide/{ => _drafts}/cran-msrv.qmd | 0 user-guide/{ => _drafts}/cran-publishing.qmd | 0 user-guide/{ => _drafts}/default-args.qmd | 0 user-guide/{ => _drafts}/faq.qmd | 0 .../{type-mapping => _drafts}/into-list.qmd | 0 .../missing-values.qmd | 0 .../{r-pkgs => _drafts}/package-setup.qmd | 0 .../{type-mapping => _drafts}/scalars.qmd | 0 .../{ => _drafts}/serde-integration.qmd | 0 user-guide/{ => _drafts}/tokio.qmd | 0 .../{type-mapping => _drafts}/vectors.qmd | 0 user-guide/{ => _drafts}/webr.qmd | 0 user-guide/complete-example.qmd | 185 +++++--- user-guide/index.qmd | 47 +- user-guide/package-structure.qmd | 199 +++++++++ user-guide/r-pkgs/package-structure.qmd | 52 --- user-guide/type-mapping/extendr-macro.qmd | 401 ------------------ 20 files changed, 342 insertions(+), 542 deletions(-) rename user-guide/{error-handling => _drafts}/basic-error-handling.qmd (100%) rename user-guide/{type-mapping => _drafts}/characters.qmd (100%) rename user-guide/{type-mapping => _drafts}/collections.qmd (100%) rename user-guide/{ => _drafts}/cran-msrv.qmd (100%) rename user-guide/{ => _drafts}/cran-publishing.qmd (100%) rename user-guide/{ => _drafts}/default-args.qmd (100%) rename user-guide/{ => _drafts}/faq.qmd (100%) rename user-guide/{type-mapping => _drafts}/into-list.qmd (100%) rename user-guide/{type-mapping => _drafts}/missing-values.qmd (100%) rename user-guide/{r-pkgs => _drafts}/package-setup.qmd (100%) rename user-guide/{type-mapping => _drafts}/scalars.qmd (100%) rename user-guide/{ => _drafts}/serde-integration.qmd (100%) rename user-guide/{ => _drafts}/tokio.qmd (100%) rename user-guide/{type-mapping => _drafts}/vectors.qmd (100%) rename user-guide/{ => _drafts}/webr.qmd (100%) create mode 100644 user-guide/package-structure.qmd delete mode 100644 user-guide/r-pkgs/package-structure.qmd delete mode 100644 user-guide/type-mapping/extendr-macro.qmd diff --git a/user-guide/error-handling/basic-error-handling.qmd b/user-guide/_drafts/basic-error-handling.qmd similarity index 100% rename from user-guide/error-handling/basic-error-handling.qmd rename to user-guide/_drafts/basic-error-handling.qmd diff --git a/user-guide/type-mapping/characters.qmd b/user-guide/_drafts/characters.qmd similarity index 100% rename from user-guide/type-mapping/characters.qmd rename to user-guide/_drafts/characters.qmd diff --git a/user-guide/type-mapping/collections.qmd b/user-guide/_drafts/collections.qmd similarity index 100% rename from user-guide/type-mapping/collections.qmd rename to user-guide/_drafts/collections.qmd diff --git a/user-guide/cran-msrv.qmd b/user-guide/_drafts/cran-msrv.qmd similarity index 100% rename from user-guide/cran-msrv.qmd rename to user-guide/_drafts/cran-msrv.qmd diff --git a/user-guide/cran-publishing.qmd b/user-guide/_drafts/cran-publishing.qmd similarity index 100% rename from user-guide/cran-publishing.qmd rename to user-guide/_drafts/cran-publishing.qmd diff --git a/user-guide/default-args.qmd b/user-guide/_drafts/default-args.qmd similarity index 100% rename from user-guide/default-args.qmd rename to user-guide/_drafts/default-args.qmd diff --git a/user-guide/faq.qmd b/user-guide/_drafts/faq.qmd similarity index 100% rename from user-guide/faq.qmd rename to user-guide/_drafts/faq.qmd diff --git a/user-guide/type-mapping/into-list.qmd b/user-guide/_drafts/into-list.qmd similarity index 100% rename from user-guide/type-mapping/into-list.qmd rename to user-guide/_drafts/into-list.qmd diff --git a/user-guide/type-mapping/missing-values.qmd b/user-guide/_drafts/missing-values.qmd similarity index 100% rename from user-guide/type-mapping/missing-values.qmd rename to user-guide/_drafts/missing-values.qmd diff --git a/user-guide/r-pkgs/package-setup.qmd b/user-guide/_drafts/package-setup.qmd similarity index 100% rename from user-guide/r-pkgs/package-setup.qmd rename to user-guide/_drafts/package-setup.qmd diff --git a/user-guide/type-mapping/scalars.qmd b/user-guide/_drafts/scalars.qmd similarity index 100% rename from user-guide/type-mapping/scalars.qmd rename to user-guide/_drafts/scalars.qmd diff --git a/user-guide/serde-integration.qmd b/user-guide/_drafts/serde-integration.qmd similarity index 100% rename from user-guide/serde-integration.qmd rename to user-guide/_drafts/serde-integration.qmd diff --git a/user-guide/tokio.qmd b/user-guide/_drafts/tokio.qmd similarity index 100% rename from user-guide/tokio.qmd rename to user-guide/_drafts/tokio.qmd diff --git a/user-guide/type-mapping/vectors.qmd b/user-guide/_drafts/vectors.qmd similarity index 100% rename from user-guide/type-mapping/vectors.qmd rename to user-guide/_drafts/vectors.qmd diff --git a/user-guide/webr.qmd b/user-guide/_drafts/webr.qmd similarity index 100% rename from user-guide/webr.qmd rename to user-guide/_drafts/webr.qmd diff --git a/user-guide/complete-example.qmd b/user-guide/complete-example.qmd index 4fad36e..addaa50 100644 --- a/user-guide/complete-example.qmd +++ b/user-guide/complete-example.qmd @@ -1,40 +1,52 @@ --- title: "A Complete Example" subtitle: "A package from start to finish: Making a heckin' case converter." +execute: + eval: false --- -The Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions. - -In this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. - -This tutorial covers: - -- vectorization -- `NA` handling -- code generation using a macro +The Rust crate ecosystem is rich with very small and very powerful utility +libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It +provides traits and structs to perform some of the most common case conversions. +In this tutorial we'll create a zero-dependency R package to provide the common +case conversions. This is mainly designed to give you a sense of the development +workflow and also showcase what can be done with extendr. The resulting R +package will be more performant but less flexible than the R package +[`{snakecase}`](https://tazinho.github.io/snakecase/). ## Getting started -Create a new R package: +The first thing we need to do is create a new R package: -```r +``` r usethis::create_package("heck") ``` -When the new R package has opened up, add `extendr`. +Once we have the basic R package setup, then we need to add the necessary +`extendr` scaffolding that will link our Rust code to R and ensure the package +builds properly. -```r +``` r rextendr::use_extendr(crate_name = "rheck", lib_name = "rheck") ``` ::: callout-note -When adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`. +Normally, the crate and library names will default to the R package name, but in +this case, we cannot do that because it would create a recursive dependency in +the Rust library. To get around this, we name the internal Rust crate and +library `rheck`, which allows us to name the R package `{heck}`. ::: -Next, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started. +Next, `heck` is needed as a dependency. The easiest way to do that is to call + +``` r +rextendr::use_crate("heck") +``` +If you prefer to use your terminal, however, simply navigate to `src/rust` and +run `cargo add heck`. With this, you have everything you need to get started. -## snek case conversion +## Scalar snek case conversion ```{r, include = FALSE} library(rextendr) @@ -45,7 +57,9 @@ knitr::opts_chunk$set(engine.opts = list(dependencies = list(heck = "0.5.0"))) use heck::ToSnekCase; ``` -Let's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`. +Let's start by creating a simple function to take a single string, and convert +it to snake case. First, the trait `ToSnekCase` needs to be imported so that the +method `to_snek_case()` is available to `&str`. ```{extendrsrc} use heck::ToSnekCase; @@ -56,60 +70,71 @@ fn to_snek_case(x: &str) -> String { } ``` -Simple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. +Simple enough, right? Let's give it a shot. To make it accessible from your R +session, it needs to be included in your `extendr_module! {}` macro. -```rust +``` rust extendr_module! { mod heck; fn to_snek_case; } ``` -From your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it! +From your R session, run `devtools::document()` followed by +`devtools::load_all()` to make the function available. ```{r} to_snek_case("MakeMe-Snake case") ``` -Rarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. +Rarely is it useful to run a function on just a scalar character value. Rust, +though, works with scalars by default and adding vectorization is another step. ```{r, error = TRUE} to_snek_case(c("DontStep", "on-Snek")) ``` -Providing a character vector causes an error. So how do you go about vectorizing? +Providing a character vector causes an error. So how do you go about +vectorizing? -## vectorizing snek case conversion +## Vectorizing snek case conversion -To vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this: +To vectorize this function, you need to apply the conversion to each element in +a character vector. The extendr wrapper struct for a character vector is called +`Strings`. To take in a character vector and also return one, the function +signature should look like this: -```rust +``` rust #[extendr] fn to_snek_case(x: Strings) -> Strings { } ``` -This says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector). +This says there is an argument `x` which must be a character vector and this +function must also return the `Strings` (a character vector). The return type is +signaled by `->`. To iterate through this you can use the `.into_iter()` method +on the character vector. -To iterate through this you can use the `.into_iter()` method on the character vector. - -```rust +``` rust #[extendr] fn to_snek_case(x: Strings) -> Strings { - x - .into_iter() - // the rest of the function + x.into_iter() + // the rest of the function } ``` -Iterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. - +Iterators have a method called `.map()` (yes, just like `purrr::map()`). It lets +you apply a closure (an anonymous function) to each element of the iterator. In +this case, each element is an +[`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). +The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You +can take this slice and pass it on to `.to_snek_case()`. After having mapped +over each element, the results are `.collect()`ed into another `Strings`. ```{extendrsrc preamble = "use_heck"} #[extendr] fn to_snek_case(x: Strings) -> Strings { - x - .into_iter() + x.into_iter() .map(|xi| { xi.as_str().to_snek_case() }) @@ -117,27 +142,34 @@ fn to_snek_case(x: Strings) -> Strings { } ``` - -This new version of the function can be used in a vectorized manner: +This new version of the function can be used in a vectorized manner: ```{r} to_snek_case(c("DontStep", "on-Snek")) ``` -But can it handle a missing value out of the box? +But can it handle a missing value out of the box? ```{r} to_snek_case(c("DontStep", NA_character_, "on-Snek")) ``` -Well, sort of. The `as_str()` method when used on a missing value will return `"NA"` which is not in a user's best interest. - +Well, sort of. The `as_str()` method when used on a missing value will return +`"NA"` which is not in a user's best interest. -## handling missing values +## Handling missing values -Instead of returning `"na"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. +Instead of returning `"na"`, it would be better to return an *actual* missing +value. Those can be created using each scalar's `na()` method e.g. `Rstr::na()`. -You can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. +You can modify the `.map()` statement to check if an `NA` is present, and, if +so, return an `NA` value. To perform this check, use the `is_na()` method which +returns a `bool`, a value that is either `true` or `false`. The result can then +be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is +missing, the match arm returns the `NA` scalar value. When it is not missing, +the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` +the other `false` arm must *also* be an `Rstr`. To accomplish this use the +`Rstr::from()` method. ```{extendrsrc preamble = "use_heck", profile="release"} #[extendr] @@ -151,23 +183,27 @@ fn to_snek_case(x: Strings) -> Strings { } ``` -This function can now handle missing values! +This function can now handle missing values! ```{r} to_snek_case(c("DontStep", NA_character_, "on-Snek")) ``` -## automating other methods with a macro! - -There are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. +## Automating code-writing with a macro! -A macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. +There are traits for the other case conversions such as `ToKebabCase`, +`ToPascalCase`, `ToShoutyKebabCase` and others, with each having a similar +method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. +You can either copy the above code for `to_snek_case()` and change the method +call multiple times, *or* you can use a macro as a form of code generation. A +macro allows you to generate code in a short hand manner. This macro takes an +identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. -```rust +``` rust macro_rules! make_heck_fn { ($fn_name:ident) => { - #[extendr] /// @export + #[extendr] fn $fn_name(x: Strings) -> Strings { x.into_iter() .map(|xi| match xi.is_na() { @@ -180,7 +216,9 @@ macro_rules! make_heck_fn { } ``` -The `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported. +The `$fn_name` placeholder is put as the function name definition which is the +same as the method name. To use this macro to generate the rest of the functions +the other traits need to be imported. ```{extendrsrc heck_traits} use heck::{ @@ -191,9 +229,10 @@ use heck::{ }; ``` -With the traits in scope, the macro can be invoked to generate the other functions. +With the traits in scope, the macro can be invoked to generate the other +functions. -```rust +``` rust make_heck_fn!(to_snek_case); make_heck_fn!(to_shouty_snake_case); make_heck_fn!(to_kebab_case); @@ -204,7 +243,8 @@ make_heck_fn!(to_train_case); make_heck_fn!(to_title_case); ``` -Note that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. +Note that each of these functions should be added to the `extendr_module! {}` +macro in order for them to be available from R. ```{extendrsrc preamble = "heck_traits", include = FALSE} #[extendr] @@ -218,21 +258,24 @@ fn to_shouty_kebab_case(x: Strings) -> Strings { } ``` -Test it out with the `to_shouty_kebab_case()` function! +Test it out with the `to_shouty_kebab_case()` function! ```{r} to_shouty_kebab_case("lorem:IpsumDolor__sit^amet") ``` -And with that, you've created an R package that provides case conversion using heck and with very little code! +And with that, you've created an R package that provides case conversion using +heck, and you've done it with just a few lines of code! +## Bench marking with `{snakecase}` -## bench marking with `{snakecase}` - -To illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`. +To illustrate the performance gains from using a vectorized Rust funciton, a +`bench::mark()` is created between `to_snek_case()` and +`snakecase::to_snake_case()`. ```{r include=FALSE} -rextendr::rust_source(code = r"( +rextendr::rust_source( + code = r"( use heck::ToSnekCase; #[extendr] @@ -244,10 +287,13 @@ fn to_snek_case(x: Strings) -> Strings { }) .collect::() } -)", dependencies = list("heck" = "*"), profile = "release") +)", + dependencies = list("heck" = "*"), + profile = "release" +) ``` -The bench mark will use 5000 randomly generated lorem ipsum sentences. +The bench mark will use 5000 randomly generated lorem ipsum sentences. ```{r, warning = FALSE} x <- unlist(lorem::ipsum(5000, 1, 25)) @@ -261,17 +307,18 @@ bench::mark( ``` ::: callout-note -The memory usage for rust-based functions is likely quite understated. This is because memory allocated outside of R cannot be tracked. - -See [bench::mark](https://bench.r-lib.org/reference/mark.html#value) documentation for more. +The memory usage for rust-based functions is likely quite understated. This is +because memory allocated outside of R cannot be tracked. +See [bench::mark](https://bench.r-lib.org/reference/mark.html#value) +documentation for more information. ::: - ## The whole thing -In just 42 lines of code (empty lines included), you can create a very performant R package! +In just 42 lines of code (empty lines included), you can create a very +performant R package! -```rust +``` rust use extendr_api::prelude::*; use heck::{ diff --git a/user-guide/index.qmd b/user-guide/index.qmd index 20096a9..f48f0ea 100644 --- a/user-guide/index.qmd +++ b/user-guide/index.qmd @@ -1,19 +1,16 @@ --- title: "User Guide" -subtitle: "A comprehensive guide to using extendr." -toc: false +subtitle: "A comprehensive guide to using extendr" +page-layout: article +toc: true +toc-depth: 0 +date: today +date-format: long --- -This user guide serves two primary functions: - -- First, and more generally, it explains how to call Rust code in R (and, as - it happens, how to call R code in Rust). -- Second, and more narrowly, it provides sufficient information and resources - to empower developers to build R packages with Rust extensions and publish - them to CRAN. - -Given these goals, the guide is primarily geared towards R users and R -developers who also have a decent grasp of Rust. +This user guide focuses on developing R packages that call Rust code and +publishing them to CRAN, so it is assumed that the reader has a basic +understanding of Rust. ## What's in this guide? @@ -21,15 +18,25 @@ We're working on that... ## What's *not* in this guide? -This book is not intended to be a comprehensive introduction to Rust. While do -go over some of the basics, more often than not, concepts are introduced only to -help explain extendr tools. That said, we do try to include copious references -and links to additional resources that the reader can follow to learn more about -specific topics. Of course, the biggest reference will be ["The -Book"](). +This book is not intended to be a comprehensive introduction to Rust. While we +do go over some of the basics, more often than not, concepts are introduced only +to help explain extendr tools. To learn more about Rust, you should consult +["The Book"](). This book is also not intended to be a comprehensive guide to R package development! There are, quite frankly, better resources for that, notably [R Packages (2e)](). As with Rust specifics, we will strive -to point you in the right direction to get more details and advice when you need -it. +to point you in the right direction to get more details and advice if you should +need it. + +## Software requirements + +This guide assumes the following versions of necessary software: + +- R: {{< var version.r >}} +- Rust {{< var version.rust >}} +- extendr-api {{< var version.extendr >}} +- rextendr {{< var version.rextendr >}} + +Please see [Get Started](get-started.qmd) if you have not already installed this +software. \ No newline at end of file diff --git a/user-guide/package-structure.qmd b/user-guide/package-structure.qmd new file mode 100644 index 0000000..45d7ec2 --- /dev/null +++ b/user-guide/package-structure.qmd @@ -0,0 +1,199 @@ +--- +title: "Project Structure" +--- + +When you run `rextendr::use_extendr()` for the first time, it will generate the +scaffolding needed to integrate Rust code into an R package. The result is a +directory structure that is fairly unique among R packages, so this page serves +to walk you through all the generated files, explaining what they do. The +package directory should have the following structure: + +``` +. +β”œβ”€β”€ R +β”‚ └── extendr-wrappers.R +β”œβ”€β”€ cleanup +β”œβ”€β”€ cleanup.win +β”œβ”€β”€ configure +β”œβ”€β”€ configure.win +β”œβ”€β”€ src +β”‚ β”œβ”€β”€ .gitignore +β”‚ β”œβ”€β”€ Makevars.in +β”‚ β”œβ”€β”€ Makevars.win.in +β”‚ β”œβ”€β”€ entrypoint.c +β”‚ β”œβ”€β”€ -win.def +β”‚ └── rust +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ β”œβ”€β”€ document.rs +β”‚ └── lib.rs +└── tools + β”œβ”€β”€ config.R + └── msrv.R +``` + +As an R package developer, the file `R/extendr-wrappers.R` will perhaps look the +most familiar to you. This file contains auto-generated R wrapper functions that +call compiled Rust code via `.Call()`. The file is regenerated every time you +call `devtools::document()`. It should never be edited by hand. Every other file +we describe below serves to ensure that those `.Call()` actually link to your +Rust source and that your R package compiles correctly. Developers of the +extendr project have carefully crafted these scaffolding files to ensure that R +package development is easy and robust. Very few of these should ever be edited +by hand. Below we use the {{< iconify openmoji:stop-sign >}} icon to signal that +the file should NOT be edited by hand. As a general rule, however, you should +only ever mess with `Cargo.toml` and scripts in `rust/src/`. + +::: callout-note +On this page, we only describe the content of files if it is helpful to you as +an R package developer. If you want to know more about how these files are +generated and what they contain, you can have a look at the +[Scaffolding](../contributing/scaffolding.qmd) page in the Contributing guide. +::: + + +## Rust source files + +Everything associated with your Rust library crate will live in `src/rust`. Here +we will briefly describe some of the more important files contained there. + +### `~/Cargo.toml` + +The Rust package manifest defines the crate name, edition, dependencies, and +build profiles. If there are other crates you want to depend on, you will list +theme here. We provide a small utility `use_crate()` for this purpose. You +can also add them by hand or call `cargo add` in the terminal if you prefer. + +```{.toml filename="src/rust/Cargo.toml"} +[package] +name = 'hellorust' +publish = false +version = '0.1.0' +edition = '2021' +rust-version = '1.65' + +[lib] +crate-type = ['rlib', 'staticlib'] +name = 'hellorust' + +[[bin]] +name = 'document' +path = 'document.rs' + +[dependencies] +extendr-api = '*' + +[profile.release] +lto = true +codegen-units = 1 +``` + +Note that the crate is built as both an `rlib` (used by the `document` binary to +introspect exports) and a `staticlib` (linked into the R package). The `[[bin]]` +entry registers `document.rs` as an executable used to generate R wrappers at +build time. + +### `~/src/lib.rs` + +The main Rust library file where you gather Rust code to be linked into R. The +scaffolded version will initially contain a simple `hello_world()` example. + +```{.rust filename="src/rust/src/lib.rs"} +use extendr_api::prelude::*; + +/// Return string `"Hello world!"` to R. +/// @export +#[extendr] +fn hello_world() -> &'static str { + "Hello world!" +} + +// Macro to generate exports. +// This ensures exported functions are registered with R. +// See corresponding C code in `entrypoint.c`. +extendr_module! { + mod hellorust; + fn hello_world; +} +``` + +The `extendr_module!` macro controls which functions, `impl` blocks, and +submodules are exposed to R. Every function you want available in R must be +listed in an `extendr_module!`. + +### `~/document.rs` {{< iconify openmoji:stop-sign >}} + +A Rust binary that generates `R/extendr-wrappers.R` by introspecting the +exported function metadata at build time. This runs as part of the `Makevars` +build step via `cargo run --bin document`. You should never edit this file by +hand. + +## Build files + +### `src/entrypoint.c` {{< iconify openmoji:stop-sign >}} + +A small C file that forwards R's routine registration to the Rust library. This +is necessary to prevent the linker from stripping the static library. Here, too, +you should never edit this file by hand. + +### `src/Makevars.in` and `src/Makevars.win.in` {{< iconify openmoji:stop-sign >}} + +Makefile templates used to compile the Rust library as part of the R package +build process (whenever you call `R CMD INSTALL` or `devtools::install()`). These +are not used directly β€” `tools/config.R` reads them and writes the final +`src/Makevars` (or `src/Makevars.win`) at build time, interpolating placeholder +string variables that are specific to your package and operating system. As +noted above, the build step also runs `cargo run --bin document` to regenerate +`R/extendr-wrappers.R`. + +### `src/-win.def` {{< iconify openmoji:stop-sign >}} + +A Windows DLL export definitions file. It ensures the package's initialization +routine is exported correctly on Windows. + +## Configuration scripts + +These are tools that tailor the build process to the specific context of your +package and operating system. + +### `configure` and `configure.win` {{< iconify openmoji:stop-sign >}} + +Shell scripts run by R before compilation. They invoke `tools/config.R` via +`Rscript` to generate the final `Makevars` file from the `.in` template. As +their names suggest, `configure` is used on Unix and `configure.win` on Windows. + +### `tools/msrv.R` {{< iconify openmoji:stop-sign >}} + +Reads the `SystemRequirements` field from `DESCRIPTION` to determine the minimum +supported Rust version (MSRV), then verifies that the installed `rustc` and +`cargo` meet that requirement. An informative error is raised if they do not. + +### `tools/config.R` {{< iconify openmoji:stop-sign >}} + +Sources `tools/msrv.R`, then checks the `DEBUG` and `NOT_CRAN` environment +variables to determine the build profile. Reads `src/Makevars.in` (or `src/Makevars.win.in` +on Windows), substitutes all context-specific placeholder variables, and writes +the final `src/Makevars` (or `src/Makevars.win`). + +## Additional files + +### `src/.gitignore` + +This local .gitignore ignores artifacts of the build process that should not be +committed: + +```{.default filename="src/.gitignore"} +*.o +*.so +*.dll +target +.cargo +rust/vendor +Makevars +Makevars.win +``` + +### `cleanup` and `cleanup.win` {{< iconify openmoji:stop-sign >}} + +Shell scripts run by R when the package is uninstalled. They remove the +generated `src/Makevars` and `src/Makevars.win` files respectively. \ No newline at end of file diff --git a/user-guide/r-pkgs/package-structure.qmd b/user-guide/r-pkgs/package-structure.qmd deleted file mode 100644 index 94ed561..0000000 --- a/user-guide/r-pkgs/package-structure.qmd +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "Project Structure" ---- - -A extendr-powered R package has a fairly unique structure. This section briefly outlines the structure of an extendr package and the important files. - -extendr works by creating a Rust library crate in `src/rust` that is defined by `src/rust/Cargo.toml`. - -```{.toml filename="Cargo.toml"} -[package] -name = 'hellorust' -publish = false -version = '0.1.0' -edition = '2021' - -[lib] -crate-type = [ 'staticlib' ] -name = 'hellorust' - -[dependencies] -extendr-api = '*' -``` - -Note the `crate-type = [ 'staticlib' ]`. When this library is compiled, it creates a static library which can then be called from R. - - -## Controlling exports to R: `lib.rs` - -The `lib.rs` file determines what will be exposed to your R package. The `extendr_module!` macro in `lib.rs` controls what will have wrappers provided to your R package. - -```toml -extendr_module! { - mod hellorust; - fn hello_world; -} -``` - -The `mod hellorust` is the name of the R package. Additional functions, `impl`s, and modules can also be added to this macro. - -## Building the package: `Makevars` - -When creating an R package that uses compiled code, a file called `Makevars` is used. - -::: callout-note -See [Using Makevars](https://cran.r-project.org/doc/manuals/R-exts.html#Using-Makevars) in Writing R Extensions for a thorough discussion. -::: - -`Makevars` is used as a preprocessing step for compiling an R package. The files `Makevars` and `Makevars.win` compile the Rust library in `src/rust`, and link to the library. - -::: callout-tip -`Makevars` is used for *nix operating systems and `Makevars.win` is used for Windows. -::: \ No newline at end of file diff --git a/user-guide/type-mapping/extendr-macro.qmd b/user-guide/type-mapping/extendr-macro.qmd deleted file mode 100644 index 8e51b22..0000000 --- a/user-guide/type-mapping/extendr-macro.qmd +++ /dev/null @@ -1,401 +0,0 @@ ---- -title: "The extendr Macro" ---- - -The power of extendr is in its ability to use Rust from R. The `#[extendr]` macro is what determines what is exported to R from Rust. This section covers the basic usage of the `#[extendr]` macro. - -[`#[extendr]`](https://extendr.github.io/extendr/extendr_api/attr.extendr.html) is what is referred to as an [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) (which itself is a type of [procedural macro](https://doc.rust-lang.org/reference/procedural-macros.html)). An attribute macro is attached to an [item](https://doc.rust-lang.org/reference/items.html) such as a function, `struct`, `enum`, or `impl`. - -The `#[extendr]` attribute macro indicates that an item should be made available to R. However, it _can only be used_ with a function or an impl block. - -```{r, include = FALSE} -library(rextendr) -``` - -## Exporting functions - -In order to make a function available to R, two things must happen. First, the `#[extendr]` macro must be attached to the function. For example, you can create a function `answer_to_life()` - -::: {.callout-note collapse="true"} -In the Hitchhiker's Guide to the Galaxy, the number 42 is the answer to the universe. See this fun [article from Scientific American](https://www.scientificamerican.com/article/for-math-fans-a-hitchhikers-guide-to-the-number-42/) -::: - -```rust -#[extendr] -fn answer_to_life() -> i32 { - 42 -} -``` - -By adding the `#[extendr]` attribute macro to the `answer_to_life()` function, we are indicating that this function has to be compatible with R. This alone, however, does not make the function available to R. It must be made available via the `extendr_module! {}` macro in `lib.rs`. - -```rust -extendr_module! { - mod hellorust; - fn answer_to_life; -} -``` - -::: callout-tip -Everything that is made available in the `extendr_module! {}` macro in `lib.rs` must be compatible with R as indicated by the `#[extendr]` macro. Note that the module name `mod hellorust` must be the name of the R package that this is part of. If you have created your package with `rextendr::use_extendr()` this should be set automatically. See [Hello, world!](../r-pkgs/package-setup.qmd). -::: - -What happens if you try and return something that cannot be represented by R? Take this example, an enum `Shape` is defined and a function takes a string `&str`. Based on the value of the arugment, an enum variant is returned. - -```rust -#[derive(Debug)] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -fn make_shape(shape: &str) -> Shape { - match shape { - "triangle" => Shape::Triangle, - "rectangle" => Shape::Rectangle, - "pentagon" => Shape::Pentagon, - "hexagon" => Shape::Hexagon, - &_ => unimplemented!() - } -} -``` - -When this is compiled, an error occurs because extendr does not know how to convert the `Shape` enum into something that R can use. The error is fairly informative! - -```rust - | ^^^^^^^^^^ the trait `ToVectorValue` is not implemented for `Shape`, which is required by `extendr_api::Robj: From` - | - = help: the following other types implement trait `ToVectorValue`: - bool - i8 - i16 - i32 - i64 - usize - u8 - u16 - and 45 others - = note: required for `extendr_api::Robj` to implement `From` - = note: this error originates in the attribute macro `extendr` -``` - -It tells you that `Shape` does not implement the `ToVectorValue` trait. The `ToVectorValue` trait is what enables items from Rust to be returned to R. - -## `ToVectorValue` trait - -In order for an item to be returned from a function marked with the `#[extendr]` attribute macro, it must be able to be turned into an R object. In extendr, the struct `Robj` is a catch all for any type of R object. - -::: callout-note -For those familiar with PyO3, the `Robj` struct is similar in concept to the [`PyAny`](https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html) struct. -::: - -The `ToVectorValue` trait is what is used to convert Rust items into R objects. The trait is implemented on a number of standard Rust types such as `i32`, `f64`, `usize`, `String` and more (see [all foreign implementations here](https://extendr.github.io/extendr/extendr_api/robj/into_robj/trait.ToVectorValue.html#foreign-impls)) which enables these functions to be returned from a Rust function marked with `#[extendr]`. - -::: callout-note -In essence, all items that are returned from a function must be able to be turned into an `Robj`. Other extendr types such as `List`, for example, have a `From for Robj` implementation that defines how it is converted into an `Robj`. -::: - -This means that with a little extra work, the `Shape` enum can be returned to R. To do so, the `#[extendr]` macro needs to be added to an impl block. - - -## Exporting `impl` blocks - -The other supported item that can be made available to R is an [`impl`](https://doc.rust-lang.org/std/keyword.impl.html) block. -`impl` is a keyword that allows you to _implement_ a trait or an inherent implementation. The `#[extendr]` macro works with inherent implementations. These are `impl`s on a type such as an `enum` or a `struct`. extendr _does not_ support using `#[extendr]` on trait impls. - -::: callout-note -You can only add an inherent implementation on a type that you have own and not provided by a third party crate. This would violate the [orphan rules](https://github.com/Ixrec/rust-orphan-rules?tab=readme-ov-file#what-are-the-orphan-rules). -::: - -Continuing with the `Shape` example, this enum alone cannot be returned to R. For example, the following code will result in a compilation error - -```rust -#[derive(Debug)] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -fn make_shape(shape: &str) -> Shape { - match shape { - "triangle" => Shape::Triangle, - "rectangle" => Shape::Rectangle, - "pentagon" => Shape::Pentagon, - "hexagon" => Shape::Hexagon, - &_ => unimplemented!() - } -} -``` -``` -error[E0277]: the trait bound `Shape: ToVectorValue` is not satisfied - --> src/lib.rs:19:1 - | -19 | #[extendr] - | ^^^^^^^^^^ the trait `ToVectorValue` is not implemented for `Shape`, which is required by `extendr_api::Robj: From` - | -``` - -However, you add the `#[extendr]` attribute to the `Shape` enum, it can be returned to R. - -```{extendrsrc} -#[derive(Debug)] -#[extendr] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -fn make_shape(shape: &str) -> Shape { - match shape { - "triangle" => Shape::Triangle, - "rectangle" => Shape::Rectangle, - "pentagon" => Shape::Pentagon, - "hexagon" => Shape::Hexagon, - &_ => unimplemented!() - } -} - -``` - -It is also possible to add methods to structs/enums and their instances: - -```{extendrsrc} -#[derive(Debug)] -#[extendr] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -impl Shape { - fn new(x: &str) -> Self { - match x { - "triangle" => Self::Triangle, - "rectangle" => Self::Rectangle, - "pentagon" => Self::Pentagon, - "hexagon" => Self::Hexagon, - &_ => unimplemented!(), - } - } - - fn n_coords(&self) -> usize { - match &self { - Shape::Triangle => 3, - Shape::Rectangle => 4, - Shape::Pentagon => 4, - Shape::Hexagon => 5, - } - } -} -``` - -In this example two new methods are added to the `Shape` enum. The first `new()` is like the `make_shape()` function that was shown earlier: it takes a `&str` and returns an enum variant. Now that the enum has an `impl` block with `#[extendr]` attribute macro, it can be exported to R by inclusion in the `extendr_module! {}` macro. - -```rust -extendr_module! { - mod hellorust; - impl Shape; -} -``` - -Doing so creates an environment in your package called `Shape`. The environment contains all of the methods that are available to you. - -::: callout-tip -There are use cases where you may not want to expose any methods but do want to make it possible to return a struct or an enum to the R. You can do this by adding an empty impl block with the `#[extendr]` attribute macro. -::: - -If you run `as.list(Shape)` you will see that there are two functions in the environment which enable you to call the methods defined in the impl block. You might think that this feel like an [R6 object](https://r6.r-lib.org/articles/Introduction.html) and you'd be right because an R6 object essentially is an environment! - -```{r} -as.list(Shape) -``` - -Calling the `new()` method instantiates a new enum variant. - -```{r} -tri <- Shape$new("triangle") -tri -``` - -The newly made `tri` object is an [external pointer](https://cran.r-project.org/doc/manuals/R-exts.html#External-pointers-and-weak-references) to the `Shape` enum in Rust. This pointer has the same methods as the Shape environmentβ€”though they cannot be seen in the same way. For example you can run the `n_coords()` method on the newly created object. - -```{r} -tri$n_coords() -``` - -::: callout-tip -To make the methods visible to the `Shape` class you can define a `.DollarNames` method which will allow you to preview the methods and attributes when using the `$` syntax. This is very handy to define when making an impl a core part of your package. - -```{r} -.DollarNames.Shape = function(env, pattern = "") { - ls(Shape, pattern = pattern) -} -``` - -::: - -### `impl` ownership - -Adding the `#[extendr]` macro to an impl allows the struct or enum to be made available to R as an external pointer. Once you create an external pointer, that is then owned by R. So you can only get references to it or mutable references. If you need an owned version of the type, then you will need to clone it. - -## Accessing exported `impl`s from Rust - -Invariably, if you have made an impl available to R via the `#[extendr]` macro, you may want to define functions that take the impl as a function argument. - -Due to R owning the `impl`'s external pointer, these functions cannot take an owned version of the impl as an input. For example trying to define a function that subtracts an integer from the `n_coords()` output like below returns a compiler error. - -```rust -#[extendr] -fn subtract_coord(x: Shape, n: i32) -> i32 { - (x.n_coords() as i32) - n -} -``` -``` -the trait bound `Shape: extendr_api::FromRobj<'_>` is not satisfied - --> src/lib.rs:53:22 - | - | fn subtract_coord(x: Shape, n: i32) -> i32 { - | ^^^^^ the trait `extendr_api::FromRobj<'_>` is not implemented for `Shape` - | -help: consider borrowing here - | - | fn subtract_coord(x: &Shape, n: i32) -> i32 { - | + - | fn subtract_coord(x: &mut Shape, n: i32) -> i32 { - | ++++ -``` - -As most often, the compiler's suggestion is a good one. Use `&Shape` to use a reference. - -## `ExternalPtr`: returning arbitrary Rust types - -In the event that you need to return a Rust type to R that doesn't have a compatible impl or is a type that you don't own, you can use `ExternalPtr`. The `ExternalPtr` struct allows any item to be captured as a pointer and returned to R. - -Here, for example, an `ExternalPtr` is returned from the `shape_ptr()` function. - -::: callout-tip -Anything that is wrapped in `ExternalPtr` must implement the `Debug` trait. -::: - -```{extendrsrc} -#[derive(Debug)] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -fn shape_ptr(shape: &str) -> ExternalPtr { - let variant = match shape { - "triangle" => Shape::Triangle, - "rectangle" => Shape::Rectangle, - "pentagon" => Shape::Pentagon, - "hexagon" => Shape::Hexagon, - &_ => unimplemented!(), - }; - - ExternalPtr::new(variant) -} -``` - -Using an external pointer, however, is far more limiting than the `impl` block. For example, you cannot access any of its methods. - -```{r, error = TRUE} -tri_ptr <- shape_ptr("triangle") -tri_ptr$n_coords() -``` - -To use an `ExternalPtr`, you have to go through a bit of extra work for it. - -```{extendrsrc, include = FALSE} -#[derive(Debug)] -enum Shape { - Triangle, - Rectangle, - Pentagon, - Hexagon, -} - -#[extendr] -impl Shape { - fn new(x: &str) -> Self { - match x { - "triangle" => Self::Triangle, - "rectangle" => Self::Rectangle, - "pentagon" => Self::Pentagon, - "hexagon" => Self::Hexagon, - &_ => unimplemented!(), - } - } - - fn n_coords(&self) -> usize { - match &self { - Shape::Triangle => 3, - Shape::Rectangle => 4, - Shape::Pentagon => 4, - Shape::Hexagon => 5, - } - } -} - -#[extendr] -fn shape_ptr(shape: &str) -> ExternalPtr { - let variant = match shape { - "triangle" => Shape::Triangle, - "rectangle" => Shape::Rectangle, - "pentagon" => Shape::Pentagon, - "hexagon" => Shape::Hexagon, - &_ => unimplemented!(), - }; - - ExternalPtr::new(variant) -} - -#[extendr] -fn n_coords_ptr(x: Robj) -> i32 { - let shape = TryInto::>::try_into(x); - - match shape { - Ok(shp) => shp.n_coords() as i32, - Err(_) => 0 - } -} -``` - -```rust -#[extendr] -fn n_coords_ptr(x: Robj) -> i32 { - let shape = TryInto::>::try_into(x); - - match shape { - Ok(shp) => shp.n_coords() as i32, - Err(_) => 0 - } -} -``` - -This function definition takes an `Robj` and from it, tries to create an `ExternalPtr`. Then, if the conversion did not error, it returns the number of coordinates as an `i32` (R's version of an integer) and if there was an error converting, it returns 0. - -```{r} -tri_ptr <- shape_ptr("triangle") - -n_coords_ptr(tri_ptr) - -n_coords_ptr(list()) -``` - -For a good example of using `ExternalPtr` within an R package, refer to the [`b64` R package](https://github.com/extendr/b64). From 5d68111161b40a81f47bf56fb5c5c056fa7f32a5 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 08:18:23 -0600 Subject: [PATCH 10/20] add consistent styling with new palette --- css/extendr.scss | 324 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 css/extendr.scss diff --git a/css/extendr.scss b/css/extendr.scss new file mode 100644 index 0000000..a2388ac --- /dev/null +++ b/css/extendr.scss @@ -0,0 +1,324 @@ +/*-- scss:uses --*/ +@use "sass:color"; +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap'); + +/*-- scss:defaults --*/ +$min-contrast-ratio: 4.5 !default; + +// bootstrap grays +$white: #fff !default; +$gray-100: #f8f9fa !default; +$gray-200: #ecf0f1 !default; +$gray-300: #dee2e6 !default; +$gray-400: #ced4da !default; +$gray-500: #b4bcc2 !default; +$gray-600: #95a5a6 !default; +$gray-700: #7b8a8b !default; +$gray-800: #343a40 !default; +$gray-900: #212529 !default; +$black: #000 !default; + +// base colors +$true-r-blue-1: #276DC3; +$true-r-blue-2: #165CAA; +$desaturated-r-blue-1: #586D99; +$desaturated-r-blue-2: #1A2740; +$rust-orange: #D34516; +$rust-silver: #67737A; +$flatly-yellow: #f39c12; + +// base extendr colors +// the blues are desaturated versions of the original r blues +// tint scales (4 = original, 1 = 75% tinted) +$dark-blue-4: $desaturated-r-blue-2; +$dark-blue-3: color.mix(white, $dark-blue-4, 25%, $method: oklch); +$dark-blue-2: color.mix(white, $dark-blue-4, 50%, $method: oklch); +$dark-blue-1: color.mix(white, $dark-blue-4, 75%, $method: oklch); + +$orange-4: $rust-orange; +$orange-3: color.mix(white, $orange-4, 25%, $method: oklch); +$orange-2: color.mix(white, $orange-4, 50%, $method: oklch); +$orange-1: color.mix(white, $orange-4, 75%, $method: oklch); + +$blue-4: $desaturated-r-blue-1; +$blue-3: color.mix(white, $blue-4, 25%, $method: oklch); +$blue-2: color.mix(white, $blue-4, 50%, $method: oklch); +$blue-1: color.mix(white, $blue-4, 75%, $method: oklch); + +$silver-4: $rust-silver; +$silver-3: color.mix(white, $silver-4, 25%, $method: oklch); +$silver-2: color.mix(white, $silver-4, 50%, $method: oklch); +$silver-1: color.mix(white, $silver-4, 75%, $method: oklch); + +$teal-4: #96C5B0; +$teal-3: color.mix(white, $teal-4, 25%, $method: oklch); +$teal-2: color.mix(white, $teal-4, 50%, $method: oklch); +$teal-1: color.mix(white, $teal-4, 75%, $method: oklch); + +// semantic colors +$primary: $dark-blue-4; +$secondary: $silver-4; +$success: $teal-4; +$danger: $orange-4; +$warning: $flatly-yellow; +$info: $blue-4; +$light: $gray-200; +$dark: $gray-800; + +// quarto callouts +$callout-color-note: $primary; +$callout-color-important: $danger; +$callout-color-tip: $success; +$callout-color-caution: color.mix($warning, $danger, 50%, $method: oklch); +$callout-color-warning: $warning; + +$link-color: color.mix(black, $teal-4, 20%, $method: oklch); +$link-hover-color: color.mix(black, $teal-4, 40%, $method: oklch); +$nav-link-color: $link-color; +$nav-link-hover-color: $link-hover-color; + +// fonts +$headings-font-family: "Noto Serif", Georgia, serif; +$font-family-sans-serif: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; + + +/*-- scss:rules --*/ +$palette: ( + 'dark-blue': ($dark-blue-1, $dark-blue-2, $dark-blue-3, $dark-blue-4), + 'orange': ($orange-1, $orange-2, $orange-3, $orange-4), + 'blue': ($blue-1, $blue-2, $blue-3, $blue-4), + 'silver': ($silver-1, $silver-2, $silver-3, $silver-4), + 'teal': ($teal-1, $teal-2, $teal-3, $teal-4), +); + +// define css custom properties for all palette colors +:root { + @each $name, $tints in $palette { + @for $i from 1 through 4 { + --#{$name}-#{$i}: #{nth($tints, $i)}; + } + } + + --r-blue-1: #{$true-r-blue-1}; + --r-blue-2: #{$true-r-blue-2}; +} + +// generate utility classes using css custom properties +@each $name, $tints in $palette { + @for $i from 1 through 4 { + .bg-#{$name}-#{$i} { + background-color: var(--#{$name}-#{$i}); + } + + .text-#{$name}-#{$i} { + color: var(--#{$name}-#{$i}); + } + + .bg-#{$name}-#{$i}-fixed { + background-color: nth($tints, $i); + } + + .text-#{$name}-#{$i}-fixed { + color: nth($tints, $i); + } + } +} + +.bg-r-blue-1 { + background-color: var(--r-blue-1); +} + +.bg-r-blue-2 { + background-color: var(--r-blue-2); +} + +.text-r-blue-1 { + color: var(--r-blue-1); +} + +.text-r-blue-2 { + color: var(--r-blue-2); +} + +.font-sans { + font-family: $font-family-sans-serif; +} + +.font-serif { + font-family: $headings-font-family; +} + +.navbar { + max-width: 90%; + margin-left: auto; + margin-right: auto; + border-radius: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + + @media (min-width: 992px) { + max-width: 65%; + } + + background-color: color.mix($gray-200, white, 20%, $method: oklch) !important; + border: 1px solid $gray-200; + color: black; + + .nav-link, + .navbar-brand { + color: black; + } + + .nav-link:hover { + color: $dark-blue-4; + } +} + +.sidebar-link.active { + color: $link-color; +} + +.card-img-col img { + width: 120px !important; + max-width: none !important; + object-fit: contain; + + @media (max-width: 576px) { + width: 92px !important; + } +} + +.guide-header { + border-bottom: 1px solid $gray-300; + margin-bottom: 1.5rem; + padding-bottom: 1rem; +} + +#homepage-header p { + margin-bottom: 0; +} + +.card { + + h1:first-child, + h2:first-child, + h3:first-child, + h4:first-child, + h5:first-child, + h6:first-child { + margin-top: 0; + } + + p:last-child { + margin-bottom: 0; + } +} + +#homepage-cards .card { + background-color: color.mix($gray-200, white, 20%, $method: oklch); + border: 2px solid $gray-200; +} + +.text-auto-dark { + color: white; +} + +.text-auto-light { + color: black; +} + +nav.page-navigation a.pagination-link:hover { + color: var(--bs-link-hover-color); +} + +.quarto-title-breadcrumbs .breadcrumb { + margin-bottom: 0.5em; + font-size: 0.8rem; + padding-left: 0; + --bs-breadcrumb-bg: ; +} + +// dark mode +body.quarto-dark { + + // swap tint scale (1↔4, 2↔3) + @each $name, $tints in $palette { + @for $i from 1 through 4 { + --#{$name}-#{$i}: #{nth($tints, 5 - $i)}; + } + } + + --bs-link-color: #{$teal-4}; + --bs-link-color-rgb: #{color.red($teal-4)}, + #{color.green($teal-4)}, + #{color.blue($teal-4)}; + --bs-link-hover-color: #{$teal-2}; + --bs-link-hover-color-rgb: #{color.red($teal-2)}, + #{color.green($teal-2)}, + #{color.blue($teal-2)}; + + .nav-link { + --bs-nav-link-color: #{$teal-4}; + } + + .sidebar-link:hover { + color: $teal-2; + } + + .sidebar-link.active { + color: $teal-4; + + &:hover { + color: $teal-2; + } + } + + .navbar { + background-color: color.mix($gray-800, $gray-900, 50%, $method: oklch) !important; + border: 2px solid $gray-800; + color: white; + + .nav-link, + .navbar-brand { + color: white; + } + + .nav-link:hover { + color: $dark-blue-1; + } + } + + .guide-header { + border-bottom-color: $gray-700; + } + + #homepage-cards .card { + background-color: color.mix($gray-800, $gray-900, 50%, $method: oklch); + border: 2px solid $gray-800; + } + + .text-auto-dark { + color: black; + } + + .text-auto-light { + color: white; + } + + .callout-icon { + filter: brightness(5); + } + + h1, + h2, + h3, + h4, + h5, + h6 { + code { + background-color: rgba(255, 255, 255, 0.1); + color: inherit; + } + } +} \ No newline at end of file From fc2920b3c6239fbbb9358a8b31f3e36c76f7963b Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 12:40:39 -0600 Subject: [PATCH 11/20] use extendr.scss for this --- css/_bootswatch.scss | 642 --------------------------------------- js/breadcrumb-fix.js | 7 - user-guide/_metadata.yml | 9 - 3 files changed, 658 deletions(-) delete mode 100644 css/_bootswatch.scss delete mode 100644 js/breadcrumb-fix.js delete mode 100644 user-guide/_metadata.yml diff --git a/css/_bootswatch.scss b/css/_bootswatch.scss deleted file mode 100644 index 1228e94..0000000 --- a/css/_bootswatch.scss +++ /dev/null @@ -1,642 +0,0 @@ -/*-- scss:defaults --*/ -// Brite 5.3.7 -// Bootswatch - -$theme: "brite" !default; - -// -// Color system -// - -$white: #fff !default; -$gray-100: #f8f9fa !default; -$gray-200: #e9ecef !default; -$gray-300: #dee2e6 !default; -$gray-400: #ced4da !default; -$gray-500: #adb5bd !default; -$gray-600: #868e96 !default; -$gray-700: #495057 !default; -$gray-800: #343a40 !default; -$gray-900: #212529 !default; -$black: #000 !default; - -$blue: #61bcff !default; -$indigo: #828df9 !default; -$purple: #be82fa !default; -$pink: #ea4998 !default; -$red: #f56565 !default; -$orange: #fa984a !default; -$yellow: #ffc700 !default; -$green: #68d391 !default; -$teal: #2ed3be !default; -$cyan: #22d2ed !default; -$lime: #a2e436 !default; - -$extendrblue: #7fc6ff; - -// $primary: $lime !default; -$primary: $extendrblue !default; -$secondary: $white !default; -$success: $green !default; -$info: $cyan !default; -$warning: $yellow !default; -$danger: $red !default; -$light: $gray-200 !default; -$dark: $black !default; - -// $min-contrast-ratio: 1.75 !default; - -$spacer: 1rem !default; - -// Body - -// Links - -$link-color: $black !default; - -// Components - -$border-radius: .375rem !default; -$border-radius-sm: .25rem !default; -$border-radius-lg: .5rem !default; - -$component-active-color: $black !default; -// $component-active-bg: $black !default; - -$focus-ring-width: 1px !default; -$focus-ring-opacity: 1 !default; -$focus-ring-color: $black !default; - -// Fonts - -$font-size-base: .875rem !default; -$font-size-sm: $font-size-base * .875 !default; - -$lead-font-weight: 400 !default; - -$headings-font-weight: 500 !default; - -$hr-border-width: 2px !default; -$hr-opacity: 1 !default; - -// Tables - -$table-cell-padding-y: .75rem !default; -$table-cell-padding-x: .75rem !default; - -$table-border-width: 1px !default; -$table-border-color: $black !default; - -$table-hover-bg-factor: 0 !default; - -$table-border-factor: 1 !default; - -$table-bg-scale: 0% !default; - -$table-variants: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark, -) !default; - -// Buttons + Forms - -$input-btn-padding-y: .5rem !default; -$input-btn-padding-x: 1rem !default; - -$input-btn-padding-y-sm: .25rem !default; -$input-btn-padding-x-sm: .75rem !default; -$input-btn-font-size-sm: $font-size-sm !default; - -$input-btn-padding-y-lg: .75rem !default; -$input-btn-padding-x-lg: 1.25rem !default; -$input-btn-font-size-lg: $font-size-base !default; - -$input-btn-border-width: 2px !default; - -// Buttons - -$btn-border-width: 2px !default; - -// Forms - -$form-label-font-weight: $headings-font-weight !default; - -$input-focus-border-color: $black !default; - -$form-check-input-checked-border-color: $black !default; - -$form-range-track-height: .6rem !default; - -$form-range-thumb-width: 1.2rem !default; -$form-range-thumb-height: $form-range-thumb-width !default; -$form-range-thumb-border: 2px solid $black !default; -$form-range-thumb-box-shadow: none !default; -$form-range-thumb-disabled-bg: $light !default; - -// Dropdowns - -// $dropdown-link-hover-color: $white !default; -// $dropdown-link-hover-bg: $primary !default; - -// Navs - -$nav-link-color: $black !default; -$nav-link-hover-color: $black !default; - -$nav-tabs-link-hover-border-color: transparent !default; - -// Navbar - -$navbar-padding-y: $spacer * .75 !default; -$navbar-padding-x: null !default; - -$navbar-nav-link-padding-x: 1rem !default; - -$navbar-light-color: $black !default; -$navbar-light-hover-color: $black !default; -$navbar-light-active-color: $black !default; -$navbar-light-disabled-color: rgba($black, .3) !default; -$navbar-light-icon-color: $black !default; -$navbar-light-toggler-border-color: $black !default; - -$navbar-dark-color: $white !default; -$navbar-dark-hover-color: $white !default; -$navbar-dark-active-color: $white !default; -$navbar-dark-disabled-color: rgba($white, .3) !default; -$navbar-dark-icon-color: $white !default; -$navbar-dark-toggler-border-color: $white !default; - -// Dropdowns - -$dropdown-border-color: $black !default; -$dropdown-border-width: 2px !default; - -$dropdown-link-hover-bg: $primary !default; - -$dropdown-header-color: $black !default; - -// Pagination - -$pagination-color: $black !default; -$pagination-hover-color: $black !default; -$pagination-hover-bg: transparent !default; -$pagination-active-border-color: $black !default; - -// Cards - -$card-border-color: $black !default; - -// Accordion - -$accordion-button-active-bg: $primary !default; -$accordion-button-active-color: $black !default; - -// Tooltips - -$tooltip-opacity: 1 !default; - -// Popovers - -$popover-border-color: $black !default; - -// Toasts - -$toast-border-color: $black !default; -$toast-box-shadow: 3px 3px 0 0 $black !default; - -$toast-header-color: $black !default; - -// Badges - -$badge-color: $black !default; - -// Modals - -$modal-content-border-color: $black !default; - -// Alerts - -// List group - -$list-group-active-border-color: $black !default; - -// Breadcrumbs - -$breadcrumb-padding-y: .5rem !default; -$breadcrumb-padding-x: 1rem !default; -$breadcrumb-divider-color: $black !default; -$breadcrumb-active-color: $black !default; -$breadcrumb-border-radius: $border-radius-lg !default; - -// Close - -$btn-close-opacity: 1 !default; -$btn-close-hover-opacity: 1 !default; - -/*-- scss:rules --*/ -// Brite 5.3.7 -// Bootswatch - - -// Variables - -:root { - --#{$prefix}border-width: 2px; - --#{$prefix}border-color: #000; -} - -[data-bs-theme="light"], -[data-bs-theme="dark"] { - --#{$prefix}border-color: #000; -} - -/*-- scss:mixins --*/ - -@mixin btn-shadow(){ - box-shadow: 3px 3px 0 0 var(--#{$prefix}border-color); -} - -table, -.table { - border: 2px solid $black !important; -} - -// Navbar - -.navbar { - $navbar-border-width: 2px; - font-size: $font-size-lg; - font-weight: $headings-font-weight; - - border: $navbar-border-width solid $black; - - &.fixed-top { - border-width: 0 0 $navbar-border-width 0; - } - - &.fixed-bottom { - border-width: $navbar-border-width 0 0 0; - } - - .dropdown-toggle::after { - margin-left: .15em; - vertical-align: .15em; - } - - &-toggler { - --bs-border-width: 2px; - } -} - -// #quarto-header, -// #quarto-header nav:first-child { -// background-color: var(--bs-light); -// } - -.navbar-title { - font-weight: 600; - font-family: monospace; -} - -.sourceCode { - @extend .border-dark; -} - -p code { - background-color: rgba(var(--bs-light-rgb), 0.1); -} - -// Buttons - -.btn { - margin: 3px 0 0 3px; - border-color: $black; - @include btn-shadow(); - transition: all .3s; - transform: translate(-3px, -3px); - - &:hover { - border-color: $black; - box-shadow: none; - transform: translate(0, 0); - } - - &.disabled { - border-color: $black; - } - - &-link, - &-link:hover { - color: $black; - } -} - -@each $color, $value in $theme-colors { - .btn-outline-#{$color} { - color: $black; - background-color: #{$value}; - box-shadow: none; - transform: translate(0, 0); - - &:hover { - @include btn-shadow(); - transform: translate(-3px, -3px); - } - } - - .btn-check + .btn-#{$color}, - .btn-check + .btn-outline-#{$color} { - &:hover { - background-color: #{$value}; - } - } -} - -.btn-outline-dark { - color: $white; -} - -.btn-group { - .btn { - margin-left: 0; - } - - > .btn.active { - z-index: 0; - } -} - -.btn-group, -.btn-group-vertical { - .btn { - &:hover, - &:active, - &:focus { - z-index: 0; - } - - } - - > .btn-check:focus + .btn, - > .btn-check:checked + .btn { - z-index: 0; - } -} - -.btn-check + .btn { - border-color: $black; - @include btn-shadow(); - transform: translate(-3px, -3px); -} - -.btn-check + .btn:hover { - color: $black; - border-color: $black; -} - -.btn-check:checked + .btn, -:not(.btn-check) + .btn:active, -.btn:first-child:active, -.btn.active, -.btn.show { - border-color: $black; - box-shadow: none; - transform: translate(0, 0); -} - -[data-bs-theme="dark"] .btn-link, -[data-bs-theme="dark"] .btn-link:hover { - color: #fff; -} - -// Typography - -a { - font-weight: $headings-font-weight; -} - -// Forms - -.form-range { - - &:not([disabled])::-moz-range-progress { - height: .6rem; - background-color: $primary; - border-radius: 6px 0 0 6px; - } - - &:not([disabled])::-ms-fill-lower { - height: .6rem; - background-color: $primary; - border-radius: 6px 0 0 6px; - } - - &::-webkit-slider-runnable-track { - border: 2px solid $black; - } - - &::-moz-range-track { - border: 2px solid $black; - } - - &::-webkit-slider-thumb { - margin-top: -.4rem; - } - - &::-moz-range-thumb { - margin-top: -.4rem; - } - - &:focus::-webkit-slider-thumb { - box-shadow: none; - } - - &:focus::-moz-range-thumb { - box-shadow: none; - } -} - -// Navs - -.nav { - &:not(.nav-tabs, .nav-pills) { - .nav-link { - &.active { - font-weight: 700; - } - } - } -} - -.nav-tabs { - gap: 4px; - padding: .4rem .4rem calc(.4rem + 2px); - font-weight: $headings-font-weight; - border: 2px solid $black; - border-radius: $border-radius-lg; - - .nav-link { - border-width: 2px; - border-radius: $border-radius; - - &.active, - &:hover { - border: 2px solid $black; - } - } -} - -.tab-content { - padding: 1rem; - margin-top: 1rem; - border: 2px solid $black; - border-radius: $border-radius-lg; -} - -.nav-pills { - gap: 4px; - font-weight: $headings-font-weight; - - .nav-link { - border: 2px solid $black; - } -} - -.breadcrumb { - font-weight: $headings-font-weight; - border: 2px solid $black; - - a { - color: $black; - } -} - -.dropdown-menu { - @include btn-shadow(); - border-radius: $border-radius; -} - -.dropdown-header { - font-weight: 700; -} - -.dropdown-item:hover, -.dropdown-item:focus { - color: #000; -} - -@each $color, $value in $theme-colors { - .list-group-item-#{$color} { - color: $black; - background-color: #{$value}; - border-color: $black; - } -} - -.list-group-item-dark { - color: $white; -} - -[data-bs-theme="dark"] { - .nav-tabs, - .nav-pills { - .nav-link { - --#{$prefix}nav-link-color: #fff; - - &:hover, - &:focus { - --#{$prefix}nav-link-hover-color: #fff; - } - } - } - - .page-link { - --#{$prefix}pagination-color: #fff; - - &:hover { - --#{$prefix}pagination-hover-color: #fff; - } - } - - .nav { - .nav-link { - --#{$prefix}nav-link-color: #fff; - - &:hover, - &:focus { - --#{$prefix}nav-link-hover-color: #fff; - } - } - } - - .breadcrumb { - a { - color: $white; - } - } - - .breadcrumb-item { - &.active { - --#{$prefix}breadcrumb-item-active-color: #fff; - } - } -} - -// Indicators - -.alert { - color: $black; - border: 2px solid $black; - @include btn-shadow(); -} - -.alert-link { - color: $black; -} - -.alert-dark { - color: $white; -} - -@each $color, $value in $theme-colors { - .alert-#{$color} { - background-color: #{$value}; - } -} - -.badge { - border: 2px solid $black; - - &.bg-dark { - color: $white; - } -} - -.progress { - border: 2px solid $black; - - &-bar { - border-right: 2px solid $black; - } -} - -.modal { - &-content { - @include btn-shadow(); - } -} - -.vr { - width: 2px; - background-color: $black; -} - -[data-bs-theme="dark"] .toast-header { - --#{$prefix}toast-header-color: #fff; -} diff --git a/js/breadcrumb-fix.js b/js/breadcrumb-fix.js deleted file mode 100644 index 55491e5..0000000 --- a/js/breadcrumb-fix.js +++ /dev/null @@ -1,7 +0,0 @@ -// Remove Quarto breadcrumb classes to allow custom styling -document.addEventListener('DOMContentLoaded', function() { - const breadcrumbNavs = document.querySelectorAll('nav.quarto-page-breadcrumbs.quarto-title-breadcrumbs'); - breadcrumbNavs.forEach(function(nav) { - nav.classList.remove('quarto-page-breadcrumbs', 'quarto-title-breadcrumbs', 'd-none', 'd-lg-block'); - }); -}); \ No newline at end of file diff --git a/user-guide/_metadata.yml b/user-guide/_metadata.yml deleted file mode 100644 index 01dec92..0000000 --- a/user-guide/_metadata.yml +++ /dev/null @@ -1,9 +0,0 @@ -format: - html: - toc: true - toc-location: right - -knitr: - opts_chunk: - collapse: true - comment: "#>" \ No newline at end of file From c7114af8dfee1ad28f9c669c4ef323354ef2adb6 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 12:41:03 -0600 Subject: [PATCH 12/20] small nav edit --- _quarto.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/_quarto.yml b/_quarto.yml index bc49de2..abd8436 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -73,10 +73,9 @@ website: contents: - user-guide/complete-example.qmd - section: "R Packages" - contents: - - user-guide/package-setup.qmd + contents: - user-guide/package-structure.qmd - - section: "CRAN" + - section: "Publishing" - id: contributing title: "Contributing" From 0aad9325b67c11a8b4fc54c3c077152733773c3f Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 12:43:29 -0600 Subject: [PATCH 13/20] fix nav link hover --- css/extendr.scss | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/css/extendr.scss b/css/extendr.scss index a2388ac..324d376 100644 --- a/css/extendr.scss +++ b/css/extendr.scss @@ -57,7 +57,7 @@ $teal-2: color.mix(white, $teal-4, 50%, $method: oklch); $teal-1: color.mix(white, $teal-4, 75%, $method: oklch); // semantic colors -$primary: $dark-blue-4; +$primary: $true-r-blue-2; $secondary: $silver-4; $success: $teal-4; $danger: $orange-4; @@ -77,6 +77,8 @@ $link-color: color.mix(black, $teal-4, 20%, $method: oklch); $link-hover-color: color.mix(black, $teal-4, 40%, $method: oklch); $nav-link-color: $link-color; $nav-link-hover-color: $link-hover-color; +$navbar-bg: color.mix($gray-200, white, 20%, $method: oklch); +$navbar-fg: black; // fonts $headings-font-family: "Noto Serif", Georgia, serif; @@ -161,9 +163,7 @@ $palette: ( max-width: 65%; } - background-color: color.mix($gray-200, white, 20%, $method: oklch) !important; border: 1px solid $gray-200; - color: black; .nav-link, .navbar-brand { @@ -262,6 +262,11 @@ body.quarto-dark { --bs-nav-link-color: #{$teal-4}; } + .sidebar nav[role=doc-toc] ul>li>a:hover, + .sidebar nav[role=doc-toc] ul>li>ul>li>a:hover { + color: #{$teal-2} !important; + } + .sidebar-link:hover { color: $teal-2; } @@ -276,7 +281,7 @@ body.quarto-dark { .navbar { background-color: color.mix($gray-800, $gray-900, 50%, $method: oklch) !important; - border: 2px solid $gray-800; + border: 1px solid $gray-800; color: white; .nav-link, @@ -287,6 +292,14 @@ body.quarto-dark { .nav-link:hover { color: $dark-blue-1; } + + .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon { + color: white !important; + } + + .quarto-color-scheme-toggle .bi { + filter: invert(1); + } } .guide-header { From c3a8d7ff9aa136aec0829192549af9046d183e5b Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 12:44:46 -0600 Subject: [PATCH 14/20] move to drafts --- user-guide/_drafts/extendr-macro.qmd | 423 +++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 user-guide/_drafts/extendr-macro.qmd diff --git a/user-guide/_drafts/extendr-macro.qmd b/user-guide/_drafts/extendr-macro.qmd new file mode 100644 index 0000000..145567b --- /dev/null +++ b/user-guide/_drafts/extendr-macro.qmd @@ -0,0 +1,423 @@ +--- +title: "The `#[extendr]` macro" +execute: + eval: false +--- + +The central task of extendr is to make Rust code available in R. Crucial to this +effort is the attribute macro, `#[extendr]`. This chapter provides a brief +introduction to the use of that macro. + +::: {.callout-note} +If you would like to learn more about Rust macros, check The Book, specifically +[Part 5 of Ch. 20](https://doc.rust-lang.org/book/ch20-05-macros.html). You may +also want to consult the section on [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html) +in the official Rust reference manual. +::: + +```{r} +#| include: false +library(rextendr) +``` + +Rust allows attribute macros to be placed on functions, struct, enums, and +impls, and extendr supports all of these. The general pattern is mostly the same +in each case, but for starters, let's consider how to apply the extendr macro to +a Rust function in order to make it available in R. In order for that to happen, +three things need to happen. First, you need to declare the extendr namespace to +the compiler with `use`. After that, you need to annotate whatever function you +want to make available in R with the `#[extendr]` macro. Finally, you must +register the function in the `extendr_module! {}` macro. + +As an example, suppose that you have created a function that gives the answer to +the question "What is the meaning of life?" and that you have very thoughtfully +decided to share this wisdom with the R community. Your main Rust library script +should then look like this: + +```{.rust filename="src/lib.rs"} +use extendr_api::prelude::*; + +#[extendr] +fn answer_to_life() -> i32 { + 42 +} + +extendr_module! { + mod hitchhiker; + fn answer_to_life; +} +``` + +Here, the `#[extendr]` macro is placed directly above the function definition. + + +## `ToVectorValue` trait + +In order for an item to be returned from a function marked with the `#[extendr]` +attribute macro, it must be able to be turned into an R object. In extendr, the +struct `Robj` is a catch all for any type of R object. + +::: callout-note +For those familiar with PyO3, the `Robj` struct is similar in concept to the +[`PyAny`](https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html) struct. +::: + +The `ToVectorValue` trait is what is used to convert Rust items into R objects. +The trait is implemented on a number of standard Rust types such as `i32`, +`f64`, `usize`, `String` and more (see [all foreign implementations +here](https://extendr.github.io/extendr/extendr_api/robj/into_robj/trait.ToVectorValue.html#foreign-impls)) +which enables these functions to be returned from a Rust function marked with +`#[extendr]`. + +::: callout-note +In essence, all items that are returned from a function must be able to be +turned into an `Robj`. Other extendr types such as `List`, for example, have a +`From for Robj` implementation that defines how it is converted into an +`Robj`. +::: + +This means that with a little extra work, the `Shape` enum can be returned to R. +To do so, the `#[extendr]` macro needs to be added to an impl block. + +## Exporting `impl` blocks + +The other supported item that can be made available to R is an +[`impl`](https://doc.rust-lang.org/std/keyword.impl.html) block. `impl` is a +keyword that allows you to *implement* a trait or an inherent implementation. +The `#[extendr]` macro works with inherent implementations. These are `impl`s on +a type such as an `enum` or a `struct`. extendr *does not* support using +`#[extendr]` on trait impls. + +::: callout-note +You can only add an inherent implementation on a type that you have own and not +provided by a third party crate. This would violate the [orphan +rules](https://github.com/Ixrec/rust-orphan-rules?tab=readme-ov-file#what-are-the-orphan-rules). +::: + +Continuing with the `Shape` example, this enum alone cannot be returned to R. +For example, the following code will result in a compilation error + +```rust +#[derive(Debug)] +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +#[extendr] +fn make_shape(shape: &str) -> Shape { + match shape { + "triangle" => Shape::Triangle, + "rectangle" => Shape::Rectangle, + "pentagon" => Shape::Pentagon, + "hexagon" => Shape::Hexagon, + &_ => unimplemented!() + } +} +``` + +``` +error[E0277]: the trait bound `Shape: ToVectorValue` is not satisfied + --> src/lib.rs:19:1 + | +19 | #[extendr] + | ^^^^^^^^^^ the trait `ToVectorValue` is not implemented for `Shape`, which is required by `extendr_api::Robj: From` + | +``` + +However, you add the `#[extendr]` attribute to the `Shape` enum, it can be +returned to R. + +```{extendrsrc} +#[derive(Debug)] +#[extendr] +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +#[extendr] +fn make_shape(shape: &str) -> Shape { + match shape { + "triangle" => Shape::Triangle, + "rectangle" => Shape::Rectangle, + "pentagon" => Shape::Pentagon, + "hexagon" => Shape::Hexagon, + &_ => unimplemented!() + } +} + +``` + +It is also possible to add methods to structs/enums and their instances: + +```{extendrsrc} +#[derive(Debug)] +#[extendr] +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +#[extendr] +impl Shape { + fn new(x: &str) -> Self { + match x { + "triangle" => Self::Triangle, + "rectangle" => Self::Rectangle, + "pentagon" => Self::Pentagon, + "hexagon" => Self::Hexagon, + &_ => unimplemented!(), + } + } + + fn n_coords(&self) -> usize { + match &self { + Shape::Triangle => 3, + Shape::Rectangle => 4, + Shape::Pentagon => 4, + Shape::Hexagon => 5, + } + } +} +``` + +In this example two new methods are added to the `Shape` enum. The first `new()` +is like the `make_shape()` function that was shown earlier: it takes a `&str` +and returns an enum variant. Now that the enum has an `impl` block with +`#[extendr]` attribute macro, it can be exported to R by inclusion in the +`extendr_module! {}` macro. + +```rust +extendr_module! { + mod hellorust; + impl Shape; +} +``` + +Doing so creates an environment in your package called `Shape`. The environment +contains all of the methods that are available to you. + +::: callout-tip +There are use cases where you may not want to expose any methods but do want to +make it possible to return a struct or an enum to the R. You can do this by +adding an empty impl block with the `#[extendr]` attribute macro. +::: + +If you run `as.list(Shape)` you will see that there are two functions in the +environment which enable you to call the methods defined in the impl block. You +might think that this feel like an [R6 +object](https://r6.r-lib.org/articles/Introduction.html) and you'd be right +because an R6 object essentially is an environment! + +```{r} +as.list(Shape) +``` + +Calling the `new()` method instantiates a new enum variant. + +```{r} +tri <- Shape$new("triangle") +tri +``` + +The newly made `tri` object is an [external +pointer](https://cran.r-project.org/doc/manuals/R-exts.html#External-pointers-and-weak-references) +to the `Shape` enum in Rust. This pointer has the same methods as the Shape +environmentβ€”though they cannot be seen in the same way. For example you can run +the `n_coords()` method on the newly created object. + +```{r} +tri$n_coords() +``` + +::: callout-tip +To make the methods visible to the `Shape` class you can define a `.DollarNames` +method which will allow you to preview the methods and attributes when using the +`$` syntax. This is very handy to define when making an impl a core part of your +package. + +```{r} +.DollarNames.Shape = function(env, pattern = "") { + ls(Shape, pattern = pattern) +} +``` +::: + +### `impl` ownership + +Adding the `#[extendr]` macro to an impl allows the struct or enum to be made +available to R as an external pointer. Once you create an external pointer, that +is then owned by R. So you can only get references to it or mutable references. +If you need an owned version of the type, then you will need to clone it. + +## Accessing exported `impl`s from Rust + +Invariably, if you have made an impl available to R via the `#[extendr]` macro, +you may want to define functions that take the impl as a function argument. + +Due to R owning the `impl`'s external pointer, these functions cannot take an +owned version of the impl as an input. For example trying to define a function +that subtracts an integer from the `n_coords()` output like below returns a +compiler error. + +```rust +#[extendr] +fn subtract_coord(x: Shape, n: i32) -> i32 { + (x.n_coords() as i32) - n +} +``` + +``` +the trait bound `Shape: extendr_api::FromRobj<'_>` is not satisfied + --> src/lib.rs:53:22 + | + | fn subtract_coord(x: Shape, n: i32) -> i32 { + | ^^^^^ the trait `extendr_api::FromRobj<'_>` is not implemented for `Shape` + | +help: consider borrowing here + | + | fn subtract_coord(x: &Shape, n: i32) -> i32 { + | + + | fn subtract_coord(x: &mut Shape, n: i32) -> i32 { + | ++++ +``` + +As most often, the compiler's suggestion is a good one. Use `&Shape` to use a +reference. + +## `ExternalPtr`: returning arbitrary Rust types + +In the event that you need to return a Rust type to R that doesn't have a +compatible impl or is a type that you don't own, you can use `ExternalPtr`. +The `ExternalPtr` struct allows any item to be captured as a pointer and +returned to R. + +Here, for example, an `ExternalPtr` is returned from the `shape_ptr()` +function. + +::: callout-tip +Anything that is wrapped in `ExternalPtr` must implement the `Debug` trait. +::: + +```{extendrsrc} +#[derive(Debug)] +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +#[extendr] +fn shape_ptr(shape: &str) -> ExternalPtr { + let variant = match shape { + "triangle" => Shape::Triangle, + "rectangle" => Shape::Rectangle, + "pentagon" => Shape::Pentagon, + "hexagon" => Shape::Hexagon, + &_ => unimplemented!(), + }; + + ExternalPtr::new(variant) +} +``` + +Using an external pointer, however, is far more limiting than the `impl` block. +For example, you cannot access any of its methods. + +```{r, error = TRUE} +tri_ptr <- shape_ptr("triangle") +tri_ptr$n_coords() +``` + +To use an `ExternalPtr`, you have to go through a bit of extra work for it. + +```{extendrsrc, include = FALSE} +#[derive(Debug)] +enum Shape { + Triangle, + Rectangle, + Pentagon, + Hexagon, +} + +#[extendr] +impl Shape { + fn new(x: &str) -> Self { + match x { + "triangle" => Self::Triangle, + "rectangle" => Self::Rectangle, + "pentagon" => Self::Pentagon, + "hexagon" => Self::Hexagon, + &_ => unimplemented!(), + } + } + + fn n_coords(&self) -> usize { + match &self { + Shape::Triangle => 3, + Shape::Rectangle => 4, + Shape::Pentagon => 4, + Shape::Hexagon => 5, + } + } +} + +#[extendr] +fn shape_ptr(shape: &str) -> ExternalPtr { + let variant = match shape { + "triangle" => Shape::Triangle, + "rectangle" => Shape::Rectangle, + "pentagon" => Shape::Pentagon, + "hexagon" => Shape::Hexagon, + &_ => unimplemented!(), + }; + + ExternalPtr::new(variant) +} + +#[extendr] +fn n_coords_ptr(x: Robj) -> i32 { + let shape = TryInto::>::try_into(x); + + match shape { + Ok(shp) => shp.n_coords() as i32, + Err(_) => 0 + } +} +``` + +```rust +#[extendr] +fn n_coords_ptr(x: Robj) -> i32 { + let shape = TryInto::>::try_into(x); + + match shape { + Ok(shp) => shp.n_coords() as i32, + Err(_) => 0 + } +} +``` + +This function definition takes an `Robj` and from it, tries to create an +`ExternalPtr`. Then, if the conversion did not error, it returns the +number of coordinates as an `i32` (R's version of an integer) and if there was +an error converting, it returns 0. + +```{r} +tri_ptr <- shape_ptr("triangle") + +n_coords_ptr(tri_ptr) + +n_coords_ptr(list()) +``` + +For a good example of using `ExternalPtr` within an R package, refer to the +[`b64` R package](https://github.com/extendr/b64). \ No newline at end of file From 41afd6bdc474e3d3e2c9702b953be3fda916eb39 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Fri, 13 Mar 2026 12:45:10 -0600 Subject: [PATCH 15/20] freeze --- .../colors-and-fonts/execute-results/html.json | 15 +++++++++++++++ .../documenting-code/execute-results/html.json | 15 +++++++++++++++ .../testing-code/execute-results/html.json | 15 +++++++++++++++ .../user-guide-pages/execute-results/html.json | 15 +++++++++++++++ .../writing-code/execute-results/html.json | 15 +++++++++++++++ .../collections/execute-results/html.json | 15 +++++++++++++++ .../control-flow/execute-results/html.json | 15 +++++++++++++++ .../enumerations/execute-results/html.json | 15 +++++++++++++++ .../functions/execute-results/html.json | 15 +++++++++++++++ .../hello-world/execute-results/html.json | 15 +++++++++++++++ .../implementations/execute-results/html.json | 15 +++++++++++++++ .../iterators/execute-results/html.json | 15 +++++++++++++++ .../intro-rust/loops/execute-results/html.json | 15 +++++++++++++++ .../mutability/execute-results/html.json | 15 +++++++++++++++ .../ownership/execute-results/html.json | 15 +++++++++++++++ .../structures/execute-results/html.json | 15 +++++++++++++++ .../intro-rust/types/execute-results/html.json | 15 +++++++++++++++ .../extendr-macro/execute-results/html.json | 15 +++++++++++++++ 18 files changed, 270 insertions(+) create mode 100644 _freeze/contributing/colors-and-fonts/execute-results/html.json create mode 100644 _freeze/contributing/documenting-code/execute-results/html.json create mode 100644 _freeze/contributing/testing-code/execute-results/html.json create mode 100644 _freeze/contributing/user-guide-pages/execute-results/html.json create mode 100644 _freeze/contributing/writing-code/execute-results/html.json create mode 100644 _freeze/intro-rust/collections/execute-results/html.json create mode 100644 _freeze/intro-rust/control-flow/execute-results/html.json create mode 100644 _freeze/intro-rust/enumerations/execute-results/html.json create mode 100644 _freeze/intro-rust/functions/execute-results/html.json create mode 100644 _freeze/intro-rust/hello-world/execute-results/html.json create mode 100644 _freeze/intro-rust/implementations/execute-results/html.json create mode 100644 _freeze/intro-rust/iterators/execute-results/html.json create mode 100644 _freeze/intro-rust/loops/execute-results/html.json create mode 100644 _freeze/intro-rust/mutability/execute-results/html.json create mode 100644 _freeze/intro-rust/ownership/execute-results/html.json create mode 100644 _freeze/intro-rust/structures/execute-results/html.json create mode 100644 _freeze/intro-rust/types/execute-results/html.json create mode 100644 _freeze/user-guide/extendr-macro/execute-results/html.json diff --git a/_freeze/contributing/colors-and-fonts/execute-results/html.json b/_freeze/contributing/colors-and-fonts/execute-results/html.json new file mode 100644 index 0000000..9fa59c8 --- /dev/null +++ b/_freeze/contributing/colors-and-fonts/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "b7798a47c2a140005aabfc4597f3a421", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Colors and Fonts\"\nengine: knitr\n---\n\n```{=html}\n\n```\n\nOur colors and themes are inspired by the painfully 90's color choices of the R\nFoundation, particularly [its logo](https://www.r-project.org/logo/), and the \nRust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). \n\n## Colors\n\nWe have defined several utility classes to make using consistent colors easier.\nThese can be used to define background and text colors. They are also used to\ndefine semantic colors, which are applied automatically. \n\n### Background colors\n\n:::::: {.d-flex}\n::: {.bg-dark-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-4\n:::\n::: {.bg-dark-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-3\n:::\n::: {.bg-dark-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-2\n:::\n::: {.bg-dark-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-orange-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-4\n:::\n::: {.bg-orange-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-3\n:::\n::: {.bg-orange-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-2\n:::\n::: {.bg-orange-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-4\n:::\n::: {.bg-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-3\n:::\n::: {.bg-blue-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-2\n:::\n::: {.bg-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-silver-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-4\n:::\n::: {.bg-silver-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-3\n:::\n::: {.bg-silver-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-2\n:::\n::: {.bg-silver-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-teal-4 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-4\n:::\n::: {.bg-teal-3 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-3\n:::\n::: {.bg-teal-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-2\n:::\n::: {.bg-teal-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-r-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-2\n:::\n::: {.bg-r-blue-1 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-1\n:::\n::::::\n\n### Text colors\n\n[.text-dark-blue-4]{.text-dark-blue-4}, [.text-dark-blue-3]{.text-dark-blue-3}, [.text-dark-blue-2]{.text-dark-blue-2}, [.text-dark-blue-1]{.text-dark-blue-1}\n\n[.text-orange-4]{.text-orange-4}, [.text-orange-3]{.text-orange-3}, [.text-orange-2]{.text-orange-2}, [.text-orange-1]{.text-orange-1}\n\n[.text-blue-4]{.text-blue-4}, [.text-blue-3]{.text-blue-3}, [.text-blue-2]{.text-blue-2}, [.text-blue-1]{.text-blue-1}\n\n[.text-silver-4]{.text-silver-4}, [.text-silver-3]{.text-silver-3}, [.text-silver-2]{.text-silver-2}, [.text-silver-1]{.text-silver-1}\n\n[.text-teal-4]{.text-teal-4}, [.text-teal-3]{.text-teal-3}, [.text-teal-2]{.text-teal-2}, [.text-teal-1]{.text-teal-1}\n\n[.text-r-blue-2]{.text-r-blue-2}, [.text-r-blue-1]{.text-r-blue-1}\n\n### Semantic colors\n\n[Primary](#){.btn .btn-primary .me-1}\n[Secondary](#){.btn .btn-secondary .me-1}\n[Success](#){.btn .btn-success .me-1}\n[Danger](#){.btn .btn-danger .me-1}\n[Warning](#){.btn .btn-warning .me-1}\n[Info](#){.btn .btn-info .me-1}\n[Light](#){.btn .btn-light .me-1}\n[Dark](#){.btn .btn-dark .me-1}\n\nThese will also apply to [Quarto callouts](https://quarto.org/docs/authoring/callouts.html).\n\n::: callout-note\nNote that there are five types of callouts, including: `note`, `tip`, `warning`, \n`caution`, and `important`.\n:::\n\n::: callout-warning\nCallouts provide a simple way to attract attention, for example, to this warning.\n:::\n\n::: callout-important\nThe callout heading is provided by the callout type, with the expected heading \n(i.e., Note, Warning, Important, Tip, or Caution).\n:::\n\n::: callout-tip\n## Tip With Title\n\nThis is an example of a callout with a title. Providing a callout heading is \noptional.\n:::\n\n::: {.callout-caution collapse=\"true\"}\n## Caution: Expand To Learn About Collapse\n\nThis is an example of a 'collapsed' caution callout that can be expanded by the \nuser. You can use `collapse=\"true\"` to collapse it by default or `collapse=\"false\"` \nto make a collapsible callout that is expanded by default.\n:::\n\nAnd they have utility classes for text colors:\n\n[.text-primary]{.text-primary}, [.text-primary-emphasis]{.text-primary-emphasis}, \n[.text-secondary]{.text-secondary}, [.text-secondary-emphasis]{.text-secondary-emphasis}, \n[.text-success]{.text-success}, [.text-success-emphasis]{.text-success-emphasis}, \n[.text-danger]{.text-danger}, [.text-danger-emphasis]{.text-danger-emphasis}, \n[.text-warning]{.text-warning}, [.text-warning-emphasis]{.text-warning-emphasis}, \n[.text-info]{.text-info}, [.text-info-emphasis]{.text-info-emphasis}, \n[.text-light]{.text-light}, [.text-light-emphasis]{.text-light-emphasis}, \n[.text-dark]{.text-dark}, [.text-dark-emphasis]{.text-dark-emphasis}, \n[.text-body]{.text-body}, [.text-body-emphasis]{.text-body-emphasis}, \n[.text-body-secondary]{.text-body-secondary}, [.text-body-tertiary]{.text-body-tertiary}\n\n## Fonts\n\nWe follow the Rust Foundation in using Noto Sans for body and smaller texts and\nNoto Serif for headers. We have also implemented utility classes for these, \nthough they should not be required.\n\n:::::: {.d-flex .gap-3}\n::: {.bg-dark-blue-3 .text-white .font-serif .p-3 .flex-fill}\n[Noto Serif]{.fs-3} \n.font-serif \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n\n::: {.bg-silver-4 .font-sans .p-3 .flex-fill .text-auto-dark}\n[Noto Sans]{.fs-3} \n.font-sans \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n::::::\n\n## Light and Dark\n\nThe `.bg-*` and `.text-*` utility classes automatically adapt to light and dark\nmode by flipping the tint scale β€” so `.text-dark-blue-4` in light mode becomes \n`.text-dark-blue-1` in dark mode, and `.text-dark-blue-1` in light mode becomes \n`.text-dark-blue-4` in dark mode. This means you generally don't need to\nthink about dark mode when using these classes. If you need a color to stay \nfixed regardless of the theme, however, simply append `-fixed` to the class \nname: `.bg-orange-4-fixed` and `.text-orange-4-fixed` will always render the \noriginal color. \n\nHere are some examples. To see how they work, toggle light and dark mode in the \nbrowser:\n\n[.bg-orange-4]{.bg-orange-4} \n[.bg-orange-4-fixed]{.bg-orange-4-fixed}\n\nNotice that the font changes from black to white in each example. This is \nBootstrap's color system adapting to the page background, which is meant to \nensure a certain contrast level for accessibility. Semantic colors (`.text-primary`, \n`.btn-success`, callouts, etc.) are also handled by Bootstrap's color system and \ndo not need any special treatment. However, you will notice that Bootstrap's \ncolor system does not recognize these utilities - currently, it does not even \nrecognize its own utilities! - so contrast can be lost when using them. To \nensure that accessibility is maintained, you may want to add `.text-auto-dark` \nor `.text-auto-light` classes, too. These will ensure that white and black text \nare correctly used with our utilities.\n\n[.bg-orange-1 with .text-auto-light]{.bg-orange-1 .text-auto-light} \n[.bg-orange-4 with .text-auto-dark]{.bg-orange-4 .text-auto-dark} \n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/contributing/documenting-code/execute-results/html.json b/_freeze/contributing/documenting-code/execute-results/html.json new file mode 100644 index 0000000..c18fc2d --- /dev/null +++ b/_freeze/contributing/documenting-code/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "2ab630bb6ca63d4ea341fb21efceb6c5", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Documenting Code\"\nengine: knitr\n---\n\nCode in extendr and rextendr should be thoroughly documented using widely\naccepted styling conventions in the R and Rust communities. \n\n## Writing style\n\n- Be concise! And stay on topic! The reader should not have to wade through\n lengthy digressions to get to the point.\n- Eschew obfuscation and avoid needlessly wordy phrases like 'eschew \n obfuscation.' In other words, keep it simple.\n- Second person (\"you\") is fine for instructions, but do not write about the \n reader's intentions in the second person (avoid expressions like \"you want to\" \n or \"you need to\").\n\n\n## Rust Documentation\n\n## R Documentation\n\nFunctions in rextendr should be documented with [roxygen2](https://roxygen2.r-lib.org), \nfollowing the conventions recommended by the [tidyverse style guide](https://style.tidyverse.org/documentation.html).\n\n- Every exported function in rextendr must include the following roxygen \n variables:\n - `@title`\n - `@description` β€” a concise description of what the function does.\n - `@param` β€” one tag per parameter, specifying:\n - the data type (e.g. `integer`, `character`, `logical`)\n - whether it is a scalar or vector\n - the default value, if one exists\n - `@return` β€” what the function returns, including the R type.\n- The `@details` and `@examples` sections are optional, but should in general be \n included.\n- Internal functions should include roxygen comments if their use in rextendr is \n fairly complex, important, and not easy to discern. Simple helper functions do\n not need to be documented.\n \n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/contributing/testing-code/execute-results/html.json b/_freeze/contributing/testing-code/execute-results/html.json new file mode 100644 index 0000000..08277e2 --- /dev/null +++ b/_freeze/contributing/testing-code/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "80bd5db10116f30dc51701d80d51de8f", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Testing Code\"\nengine: knitr\n---", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/contributing/user-guide-pages/execute-results/html.json b/_freeze/contributing/user-guide-pages/execute-results/html.json new file mode 100644 index 0000000..604e5e3 --- /dev/null +++ b/_freeze/contributing/user-guide-pages/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "3fa9b6d259a1b8f3143b11fe044bd7f7", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"User Guide Pages\"\nengine: knitr\n---\n\nUser guide pages are friendly introductions to specific features of the\n`extendr-api` crate. Keep them **concise** and use **simple language**.\nAlways use code chunks for extendr code β€” never paste raw snippets.\n\n### Page structure\n\nEach page should follow this rough structure:\n\n````markdown\n---\ntitle: \"An informative title\"\n---\n\n- Motivate the functionality the page covers\n- No more than 4 sentences\n\n## The feature\n\n- Describe the functionality\n- Discuss prerequisites if any\n\n## Basic usage\n\n- Discuss the code before showing it\n\n\n::: {.cell}\n\n```{.extendrsrc .cell-code}\n// your Rust code\n```\n:::\n\n\n- Summarize and explain the output\n\n## Advanced usage\n\n- Motivate the advanced example\n\n\n::: {.cell}\n\n```{.extendrsrc .cell-code}\n// your Rust code\n```\n:::\n\n\n- Summarize and explain\n\n## See also\n\n- Links to related pages or external docs\n````", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/contributing/writing-code/execute-results/html.json b/_freeze/contributing/writing-code/execute-results/html.json new file mode 100644 index 0000000..ce85827 --- /dev/null +++ b/_freeze/contributing/writing-code/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "eabdbdacaa25d5ec738f188b2ca6aa79", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"General writing conventions\"\nengine: knitr\n---\n\nThis page covers code formatting conventions for the extendr project. Following \nthese conventions keeps code consistent and readable across contributors.\n\n## R code\n\n- Never use `print()` to display an R object. Let R print it implicitly.\n- Use the `extendrsrc` and `extendr` knitr engines for all extendr code. See \n `user-guide/serde-integration.qmd` for a worked example to emulate.\n\n## Rust code\n\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/collections/execute-results/html.json b/_freeze/intro-rust/collections/execute-results/html.json new file mode 100644 index 0000000..1d1d45f --- /dev/null +++ b/_freeze/intro-rust/collections/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "579a7a4862437c7719c3c65c7e53088b", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Collections\"\nengine: knitr\n---\n\nRust has two primary ways to store multiple values of the same type: arrays and\nvectors. Arrays are fixed in size β€” their length is known at compile time, which \nmakes them fast but inflexible. You cannot add or remove elements after \ncreation. Vectors are like growable arrays, making them more flexible and also \nmore common in everyday Rust code. The syntax for creating arrays and vectors \nalso differs: \n\n```rust\n// array\nlet a = [10, 20, 30, 40];\n\n// vector\nlet v = vec![10, 20, 30, 40];\n```\n\nHere, the array is created using square brackets, the vector by calling the \n`vec!` macro. In this case, the types are inferred. If we want to make those\nexplicit, the syntax also differs:\n\n```rust\n// array\nlet a: [i32; 4] = [10, 20, 30, 40];\n\n// vector\nlet v: Vec = vec![10, 20, 30, 40];\n```\n\nIn this example, the array type and length are both declared with \n`[type; length]`, but the vector only requires specifying that it is a vector of \n32-bit integers or `Vec`, which can have any given length.\n\nOne nice thing about both vectors and arrays is that you can initialize them to \na specific constant value and size. The syntax here is similar, though again the\nvector version requires the use of the `vec!` macro:\n\n```rust\n// array\nlet a: [i32; 4] = [0; 4];\n\n// vector\nlet v: Vec = vec![0; 4];\n```\n\nIn each case, we put the initial value `0` followed by the size `4`.\n\nOne last thing to note here, specifically about vectors. An empty vector can \nalso be created with `Vec::new()` or `vec![]`. When creating an empty vector, \nhowever, Rust needs to know the element type either from context or from an \nexplicit type annotation, since it cannot infer type from the empty vector \nitself:\n\n```rust\nlet v: Vec = Vec::new();\n```\n\nVectors also have a number of useful **methods**: `.len()` returns the number of \nelements and `.is_empty()` returns `true` if there are no elements. We will \nencounter more methods like these as we work through the guide.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/control-flow/execute-results/html.json b/_freeze/intro-rust/control-flow/execute-results/html.json new file mode 100644 index 0000000..dd086e3 --- /dev/null +++ b/_freeze/intro-rust/control-flow/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "b29bb5b357658aa0b68f590ff85d2acf", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Control Flow\"\nengine: knitr\n---\n\nControl flow refers to the ability to conditionally execute code. As with R, \nRust declares conditions for code execution using `if`, `else` and `else if`. \nThose conditions will typically involve logical operators:\n\n- `==` check equality\n- `!=` check inequality\n- `!` negate a logical value\n- `&&` logical AND comparison\n- `||` logical OR comparison\n\nLogical operators work similarly to R but are not vectorised. They return a \nsingle logical called a `bool` instead and can only take on one of two values: \n`true` or `false`. \n\nLike R, each branch of an `if` in Rust must be delimited by curly brackets, but \nunlike R, parentheses around the condition are not required:\n\n```rust\nif x == y {\n // do something\n} else {\n // do something else\n}\n```\n\nNote, too, that the result of control flow can be assigned to a variable:\n\n```rust\nlet number = if x == y { 5 } else { 3 };\n```\n\nGiven what we learned about Rust's strong typing system, this suggests an\nadditional strong constraint on control flow: each branch or \"arm\" of an `if` \nexpression must return the same type. Otherwise, the type would be unknown at \ncompile time. Consider this example program (drawn straight from The Book) that \nincludes mismatched types in the `if` and `else` arm:. \n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let condition = true;\n\n let number = if condition { 5 } else { \"six\" };\n\n println!(\"The value of number is: {number}\");\n}\n```\n\nIf you try to compile and run this program, you will get the following error\nmessage from cargo:\n\n```default\nerror[E0308]: `if` and `else` have incompatible types\n --> src/main.rs:4:44\n |\n4 | let number = if condition { 5 } else { \"six\" };\n | - ^^^^^ expected integer, found `&str`\n | |\n | expected because of this\n\nFor more information about this error, try `rustc --explain E0308`.\n```\n\nAs an side, this is actually a great example of one of Rust's best features. Not \nonly does the error message tell you where precisely it occurs, but it also \npoints you to additional information to help you understand what is wrong in \nyour code. Often, it will even suggest how to fix your problem!\n\nFor completeness, here is the correct way to program that control flow:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let condition = true;\n\n let number = if condition { 5 } else { 6 };\n\n println!(\"The value of number is: {number}\");\n}\n```\n\n```default\nThe value of number is: 5\n```", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/enumerations/execute-results/html.json b/_freeze/intro-rust/enumerations/execute-results/html.json new file mode 100644 index 0000000..872f92a --- /dev/null +++ b/_freeze/intro-rust/enumerations/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "797ed98c9824178cf9f540efbf61e5e6", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Enumerations\"\nengine: knitr\n---\n\nSometimes a value can only take on one of a fixed set of possibilities. In R,\nthis pattern appears constantly as function arguments. For example, the `cor()`\nfunction has the argument `method = c(\"pearson\", \"kendall\", \"spearman\")`. Rust \nformalizes this idea with *enumerations* or`enum`s, types that can take on \nexactly one of a defined set of *variants*.\n\n::: {.callout-note}\nThe tidyverse design style guide has a great section on enums. See \n[enumerate options](https://design.tidyverse.org/enumerate-options.html#whats-the-pattern).\n\nJosiah Parry's blog also has a nice discussion of this. See \n[Enums in R: towards type safe R](https://josiahparry.com/posts/2023-11-10-enums-in-r/).\n:::\n\nLike structs, enum names use PascalCase, and variants are created using \n`EnumName::Variant`:\n\n```rust\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\nlet my_shape = Shape::Triangle;\n```\n\n## Matching\n\nHow do we actually determine behavior based on a variant? This is done using \n*pattern matching*, specifically the keyword `match`, which works similar to \n`switch()` in R. The pattern match format uses the syntax `Enum::Variant => action`. \nWhen using `match` each variant much be *enumerated*: \n\n```{.rust filename=\"src/main.rs\"}\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\nfn main() {\n let my_shape = Shape::Triangle;\n\n match my_shape {\n Shape::Triangle => println!(\"A triangle has 3 vertices\"),\n Shape::Rectangle => println!(\"A rectangle has 4 vertices\"),\n Shape::Pentagon => println!(\"A pentagon has 5 vertices\"),\n Shape::Hexagon => println!(\"A hexagon has 6 vertices\"),\n }\n}\n```\n\n```default\nA triangle has 3 vertices\n```\n\nWhen you only care about specific variants, use `_` as a catch-all for\neverything else:\n\n```{.rust filename=\"src/main.rs\"}\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\nfn main() {\n let my_shape = Shape::Triangle;\n\n match my_shape {\n Shape::Hexagon => println!(\"Hexagons are the bestagons\"),\n _ => println!(\"Every other polygon is mid\"),\n }\n}\n```\n\n```default\nEvery other polygon is mid\n```\n\n## Methods\n\nEnums can have `impl` blocks just like structs. Inside the method, `match self` \nbranches on the variant:\n\n```{.rust filename=\"src/main.rs\"}\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\nimpl Shape {\n fn n_vertices(&self) -> i32 {\n match self {\n Self::Triangle => 3,\n Self::Rectangle => 4,\n Self::Pentagon => 5,\n Self::Hexagon => 6,\n }\n }\n}\n\nfn main() {\n let my_shape = Shape::Triangle;\n let n_vertices = my_shape.n_vertices();\n\n println!(\"my shape has {} vertices\", n_vertices);\n}\n```\n\n```default\nmy shape has 3 vertices\n```\n\n## Missing values\n\nBecause of its memory model and strong safety guarantees, Rust has no concept of \na `NULL` type. Null values are actually quite dangerous and have their own \ncategory of errors and security issues. In fact, [C.A.R. Hoare](https://en.wikipedia.org/wiki/Tony_Hoare), \nwho invented null pointers, called them his \"billion-dollar mistake.\" Rust,\ntherefore, makes it impossible to use null values, requiring instead that the \nconcept of missingness be implemented using a special kind of `enum` known as an\n`Option<>`, which has exactly two variants:\n\n```rust\nenum Option {\n Some(T),\n None,\n}\n```\n\nAs a reminder, the `T` here stands for any generic type. When a value is \npresent, it is wrapped in `Some(T)`. When it is absent, the variant is `None`. \nThe type system forces you to acknowledge the possibility of `None` before you \ncan use the inner value β€” there is no way to accidentally treat a missing value \nas if it were present.\n\nSince `Option` is just an enum, we can match on the variants to access its \nvalues.\n\n```rust\n// create an Option that contains a value\nlet measure = Some(Measure::Euclidean);\n\nmatch measure {\n Some(v) => println!(\"The measure is: {}\", v),\n None => println!(\"Oh no! The measure is missing!\"),\n}\n```\n\nThis makes missing values visible - the code cannot compile unless both arms are \nhandled.\n\n::: {.callout-warning}\nSometimes dealing with options is a headache, particularly when we're in the \nearly stages of developing. While we can use `.unwrap()` or `.expect()` to grab \nthe inner value of an option _without matching_, this is **dangerous!!** because \nwe are ignoring the possibility of a `None`. Unwrapping on a `None` leads to a \n**panic**. Panics cause your program to abort.\n\n```rust\nthread 'main' panicked at src/main.rs:4:41:\ncalled `Option::unwrap()` on a `None` value\n```\n:::\n\n## Optional arguments\n\nA common use is to make function arguments optional β€” call with a value when you \nhave one, and fall back to a default when you do not, similar to how we use \n`function(x = NULL)` in R. This can be achieved using an option in Rust.\n\n```{.rust filename=\"src/main.rs\"}\nfn greet_user(name: Option) {\n let user_name = name.unwrap_or(String::from(\"world\"));\n println!(\"Hello, {}!\", user_name);\n}\n\nfn main() {\n greet_user(None); // Output: Hello, world!\n greet_user(Some(\"Ferris\".to_string()));\n}\n```\n\n```default\nHello, world!\nHello, Ferris!\n```\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/functions/execute-results/html.json b/_freeze/intro-rust/functions/execute-results/html.json new file mode 100644 index 0000000..c685b81 --- /dev/null +++ b/_freeze/intro-rust/functions/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "6bf9354bc9ad4eeb32f73992c6b09998", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Functions\"\nengine: knitr\n---\n\nSo far, we have been modifying the `main()` function, but Rust lets us define as \nmany functions as we need. In R, functions are objects created with \n`<- function()`. In Rust, they are declared with the `fn` keyword. Just like \neverything else in Rust, arguments are typed - each argument takes the form \n`arg_name: type`, and the return type is specified after `->`.\n\n```rust\nfn name_of_function(arg1: ArgType) -> ReturnType {\n // function body\n my_return_object\n}\n```\n\nAs in R, the last expression in a function body is returned automatically β€” no \nexplicit `return` keyword is needed unless you are returning early. Functions \ncan also be declared anywhere in the file, outside of `main()`.\n\nIdentifying whether a number is odd or even is a classic example. The\n[is-odd npm package](https://www.npmjs.com/package/is-odd) became famous for \nbeing a remarkably small and widely depended-upon piece of code:\n\n![](/images/is-odd.png){width=70% fig-align=\"center\"}\n\nHere is the Rust equivalent. We define `is_even()` first, taking an `i32` and \nreturning a `bool`, then use it inside `is_odd()`:\n\n```rust\nfn is_even(x: i32) -> bool {\n x % 2 == 0\n}\n\nfn is_odd(x: i32) -> bool {\n !is_even(x)\n}\n```\n\nSince `x % 2 == 0` is an expression that evaluates to a `bool`, it is returned \ndirectly without a `return` statement. Building new functions from existing ones \nlike this is idiomatic Rust.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/hello-world/execute-results/html.json b/_freeze/intro-rust/hello-world/execute-results/html.json new file mode 100644 index 0000000..3cf228c --- /dev/null +++ b/_freeze/intro-rust/hello-world/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "b679dd97539fd1e8e4d56b08cd59bfc1", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Hello, World!\"\nengine: knitr\n---\n\nIn this tutorial, you will learn how to setup a new Rust crate and get a sense\nfor what it includes. You will also learn how to build and run your new crate \nwith `cargo`, a package manager and build system for Rust. You do not need to \ninstall `cargo` because it comes bundled with Rust.\n\nWhen you first setup a Rust crate, you will have to choose between one of two \ntypes: binary crates and library crates. Binary crates are standalone \napplications or executables that you can run, like command line tools. Library \ncrates are typically small bundles of code that are used in the development of \nother crates. In this tutorial, we will focus on building a binary crate. \n\nTo create a new binary crate, open a terminal and run the following:\n\n```default\n$ cargo new hello-world\n``` \n\nThis will create a new directory called `hello-world` that you can then navigate \ninto using `cd hello-world`. Once inside that directory, you will find all of\nthe following:\n\n``` \nhello-world/\nβ”œβ”€β”€ Cargo.toml\nβ”œβ”€β”€ Cargo.lock\n└── src/\n └── main.rs\n```\n\nThe `Cargo.toml` file is a document in which you specify metadata and \ndependencies for your crate similar to an R package's `DESCRIPTION`. The \n`Cargo.lock` file maintains copies of those dependencies, similar to \n`renv.lock`. A lot more can be said about those two files, which are absolutely\ncrucial to programming in Rust, but for now let's focus on the Rust file in the \nsource directory. If you open that file in Positron (or your preferred IDE), you\nwill see the following text:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n println!(\"Hello, world!\");\n}\n```\n\nIn just those three short lines, we see examples of many Rust coding patterns:\n\n- Functions are declared using the `fn` keyword,\n- The `main()` function defines what happens when you run your binary,\n- Blocks of code are delimited using curly brackets (just like R), and\n- Statements end with `;`.\n\nRight now, our app uses the `println!()` macro to print \"Hello, world!\" to the \nterminal. When a program writes to the console, it does so through file \nconnections called standard output (`stdout`) and standard error (`stderr`). In \nR, when we print a message with `print()` or `message()`, we print to stdout. \nSimilarly, when we make a warning or error using `stop()` or `warning()`, that \nis writing to `stderr`. The Rust `println!()` macro also has the nice feature of \nsupporting string interpolation like `sprintf()` and `glue()`:\n\n``` rust\n// indirect interpolation\nlet name = \"Josiah\";\nprintln!(\"Hello, {}!\", name);\n\n// direct interpolation\nlet name = \"Josiah\";\nprintln!(\"Hello, {name}!\");\n```\n\nIf we add one or the other of those lines to `main()` like so\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let name = \"Josiah\";\n println!(\"Hello, {name}!\");\n}\n```\n\nwe can then call the application and get it to execute in the terminal:\n\n```default\n$ cargo run\n Compiling hello_world v0.1.0 (/hello-world)\n Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs\n Running `target/debug/hello_world`\nHello, Josiah!\n```\n\nNotice that `cargo` provides detailed information about the build process \n(printed to stderr) before actually running the program (and printing to \nstdout). This is a useful reminder that Rust, unlike R, is a compiled language - \nRust programs have to be built before they can be run. For those coming from R, \nthat might sound rather mysterious, and in some respects it is, but we want to \nemphasize how easy it was to get a program up and running. It was just three \nlines of code in the terminal:\n\n```default\n$ cargo new hello-world\n$ cd hello-world\n$ cargo run\n```\n\nThat's it! Those three lines were sufficient to build and run our simple binary. \nNow we can move on to learning about basic data types in Rust.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/implementations/execute-results/html.json b/_freeze/intro-rust/implementations/execute-results/html.json new file mode 100644 index 0000000..641f18e --- /dev/null +++ b/_freeze/intro-rust/implementations/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "ea8a511a54069ebf5106815389a6c1a0", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Implementations\"\nengine: knitr\n---\n\nOther methods can be implemented for structs using `impl` blocks. An `impl` \nblock is roughly analogous to defining S3 methods in R, except Rust's `impl` \nblocks associate behavior directly with the type definition, making them \nobject-oriented. The syntax is this:\n\n```rust\nimpl TypeName {\n fn method_name() {\n // body\n }\n}\n```\n\n## Associated functions\n\nAn *associated function* is called on the type itself rather than on an \ninstance. These are written as `TypeName::function()` and are typically used\nas constructors. The return type `Self` refers to the type being implemented:\n\n```{.rust filename=\"src/main.rs\"} \n#[derive(Debug)]\nstruct Person {\n name: String,\n age: i32\n}\n\nimpl Person {\n fn new(name: String, age: i32) -> Self {\n Person { name, age }\n }\n}\n\nfn main() {\n let me = Person::new(\"Josiah\".to_string(), 29);\n println!(\"{me:?}\");\n}\n```\n\n```default\nPerson { name: \"Josiah\", age: 29 }\n```\n\nYou have already seen associated functions in this form: `Vec::new()` and\n`String::new()` are both associated functions.\n\n## Self-reference\n\nTo define a method that operates on an instance, add `&self` as the first\nargument. This provides a read-only reference to the struct, so you can access \nits fields but not move or modify them:\n\n```rust\nimpl Person {\n fn greet(&self) {\n println!(\n \"Hello, my name is {} and I am {} years old.\",\n self.name, self.age\n );\n }\n}\n```\n\nA method can also accept another instance of the same type by using `&Self` as \nan argument type. Here `is_older_than()` compares two `Person` values:\n\n```rust\nimpl Person {\n fn is_older_than(&self, other_person: &Self) -> bool {\n self.age > other_person.age\n }\n}\n```\n\n## Mutable self\n\nTo modify a struct's fields from a method, use `&mut self`. Here `rename()` and\n`celebrate_birthday()` both mutate fields in place:\n\n```{.rust filename=\"src/main.rs\"} \n#[derive(Debug)]\nstruct Person {\n name: String,\n age: i32\n}\n\nimpl Person {\n fn new(name: String, age: i32) -> Self {\n Person { name, age }\n }\n\n fn greet(&self) {\n println!(\n \"Hello, my name is {} and I am {} years old.\",\n self.name, self.age\n );\n }\n\n fn rename(&mut self, new_name: &str) {\n self.name = new_name.to_string();\n }\n\n fn celebrate_birthday(&mut self) {\n self.age += 1;\n }\n}\n\nfn main() {\n let mut me = Person::new(\"Josiah\".to_string(), 29);\n me.celebrate_birthday();\n me.rename(\"Jo\");\n me.greet(); \n}\n```\n\n```default\nHello, my name is Jo and I am 30 years old.\n```\n\nNote that the instance must also be declared `mut` when it is assigned to a \nvariable. ", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/iterators/execute-results/html.json b/_freeze/intro-rust/iterators/execute-results/html.json new file mode 100644 index 0000000..d33f97d --- /dev/null +++ b/_freeze/intro-rust/iterators/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "4a88c3e33f91c5f183d27f5f50c8fd2f", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Iterators\"\nengine: knitr\n---\n\nSo far we have been using `for` loops to work through collections, but Rust has \na more powerful and flexible mechanism: iterators. Iterators produce a sequence \nof items that can be accessed one at a time and chained with methods like \n`.sum()`, `.min()`, `.max()`, and `.enumerate()`. They are closer in spirit to \n`apply()` or `purrr::map()` than to a traditional for-loop.\n\nAnd actually, we were technically using iterators all along, since Rust calls \n`.into_iter()` on the vector passed to a for-loop under the hood. Both of these\nare equivalent:\n\n```rust\nlet nums = vec![3, 6, 9];\n\nfor n in nums { ... }\nfor n in nums.into_iter() { ... }\n```\n\n## Borrowing\n\nOne important constraint on the use of `.into_iter()` is that it actually \n*consumes* the vector β€” ownership is moved into the iterator so that the vector \ncan no longer be used afterward:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let nums = vec![3, 6, 9];\n for n in nums.into_iter() { // ⬅️ nums is moved\n println!(\"Value: {n}\");\n }\n \n println!(\"nums: {:?}\", nums); // ❌ compiler error\n}\n```\n\nTo iterate without consuming the original collection, use `.iter()` instead. \nThis *borrows* the collection and produces references to each element, leaving \nthe original value intact:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let nums = vec![3, 6, 9];\n \n for n in nums.iter() {\n println!(\"Reference to value: {n}\");\n }\n \n println!(\"nums is still usable: {:?}\", nums);\n}\n```\n\n```default\nReference to value: 3\nReference to value: 6\nReference to value: 9\nnums is still usable: [3, 6, 9]\n```\n\nOnce you have an iterator, a number of useful methods are available. Here is an \nexample of the sum method:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let nums = vec![2, 4, 8];\n let total: i32 = nums.iter().sum();\n println!(\"Sum is: {total}\");\n}\n```\n\n```default\nSum is: 14\n```\n\n## Enumerating\n\nYou can also `.enumerate()` over iterators to get the index and value of the \ncurrent element. These are returned as a tuple `(i, xi)` where `i` is the index \nand `xi` is the value. The data type of the index `i` is `usize`, which can if \nneeded be cast to `i32` with `i as i32`. Note that Rust is zero-indexed!\n\n```{.rust filename=\"src/main.rs\"} \nfn is_even(x: i32) -> bool {\n x % 2 == 0\n}\n\nfn main() {\n let x = vec![1, 3, 5];\n\n for (i, xi) in x.iter().enumerate() {\n if is_even(i as i32) {\n println!(\"Index {i} is even with value {xi}\");\n }\n }\n}\n```\n\n```default\nIndex 0 is even with value 1\nIndex 2 is even with value 5\n```\n\n## Mapping\n\nMaybe the most useful method to apply to iterators is `.map()`, the Rust analog \nof `purrr::map()` and the `apply()` family of functions. Here is an example that \nsquares each element of a vector:\n\n```rust\n// providing explicit type annotation in one\n// value so the compiler knows its f64 and not f32\nlet x = vec![5.9_f64, 6.8, 4.5, 7.3, 6.2];\n\nx.iter().map(|xi| xi.powi(2))\n```\n\nUsing `.map()` returns another iterator, so we can chain operations over \niterators by using multiple `.map()` statements.\n\n```rust\n// providing explicit type annotation in one\n// value so the compiler knows its f64 and not f32\nlet x = vec![5.9_f64, 6.8, 4.5, 7.3, 6.2];\n\nx.iter().map(|xi| xi.powi(2)).map(|xi| xi - 1.0)\n```\n\n## Closure\n\nIn these examples we use a *closure* to modify each element of the iterator, \nwhich is Rust's version of an anonymous function. The syntax of a closure is \n`|arg| expression`, similar to R's recent adoption of `\\(arg) expression`. Like \nR's anonymous functions, they can also be multiple lines by wrapping the \nexpression in curly brackets:\n\n```rust\nx.iter().map(|xi| {\n let squared = xi.powi(2);\n squared.sqrt()\n})\n```\n\nWhen using `.enumerate().map()`, you get the Rust version of `purrr::imap()`.\n\n```rust\nx.iter()\n .enumerate()\n .map(|(i, xi)| {\n // use both i and xi here\n })\n```\n\nNotice that the iterator is now the tuple `(i, xi)` as before in the for-loop.\n\n## Collecting\n\nIn R, `purrr::map()` always returns a list, but in Rust, `.map()` always returns \nanother iterator, so getting back a concrete collection means you must `.collect()` \nthe values in the iterator. Rust cannot always infer the type you want to \ncollect, however, so you need to provide it explicitly:\n\n```rust\nlet x_squared: Vec = x.iter().map(|xi| xi.powi(2)).collect();\n```\n\nAlternatively, you can use the *turbofish* syntax `.collect::()` to \nspecify the type directly on `.collect()` rather than in the assignment:\n\n```rust\nlet x_squared = x.iter().map(|xi| xi.powi(2)).collect::>();\n```\n\nIn either case, you may use a general placeholders to encourage Rust to infer \nthe type, the syntax being `Vec<_>`:\n\n```rust\nlet x_squared: Vec<_> = x.iter().map(|xi| xi.powi(2)).collect();\nlet x_squared = x.iter().map(|xi| xi.powi(2)).collect::>();\n```\n\nThis lets Rust infer the element type while you specify the container type.", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/loops/execute-results/html.json b/_freeze/intro-rust/loops/execute-results/html.json new file mode 100644 index 0000000..05dc0c2 --- /dev/null +++ b/_freeze/intro-rust/loops/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "2932b2ee3500d0c488e7b7a66b89fc78", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Loops\"\nengine: knitr\n---\n\nIn Rust, the easiest way to iterate over items in a collection is with a `for` \nloop. The syntax should look familiar to R programmers. Each item in the \ncollection is bound to the name you provide before `in` at each iteration of the \nloop:\n\n```rust\nfor value in collection {\n // do something with value\n}\n```\n\nAs with control flow, the content of a for-loop must be delimited by curly \nbrackets, but the in-statement does not require parantheses. As an example, \nconsider a program for printing each number in a vector:\n\n```{.rust filename=\"src/main.rs\"}\nfn main() {\n let nums = vec![1, 2, 3];\n\n for n in nums {\n println!(\"n is: {n}\");\n }\n}\n```\n\nRunning `cargo run --quiet` (the `quiet` suppresses compiler logs) should print\nthe following to your terminal:\n\n```default\nn is: 1\nn is: 2\nn is: 3\n```\n\nIn the context of for-loops, Rust's scoping rules are important to be aware of.\nThey are, in fact, much stricter than R's. Like R, values declared outside a \nfor-loop are accessible inside it, but unlike R, values created inside the loop \ncannot be used outside it. In the following example, `greeting` is defined in \nthe outer scope and is accessible inside the loop:\n\n```{.rust filename=\"src/main.rs\"}\nfn main() {\n let greeting = \"Hi\";\n let names = vec![\"Alice\", \"Bob\"];\n\n for name in names {\n println!(\"{greeting}, {name}!\");\n }\n}\n```\n\n```default\nHi, Alice!\nHi, Bob!\n```\n\nIn contrast, `doubled` in the next example is defined inside the loop and cannot \nbe accessed after it ends:\n\n```{.rust filename=\"src/main.rs\"}\nfn main() {\n let numbers = vec![1, 2, 3];\n\n for n in numbers {\n let doubled = n * 2;\n println!(\"{n} doubled is {doubled}\");\n }\n\n // ❌ `doubled` doesn't exist here\n // println!(\"Last doubled: {}\", doubled);\n}\n```\n\nIn the next section we will see how to get around this scoping rule using a \nmutable variable declared in the outer scope to collect values produced inside \nthe loop.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/mutability/execute-results/html.json b/_freeze/intro-rust/mutability/execute-results/html.json new file mode 100644 index 0000000..3af2dc7 --- /dev/null +++ b/_freeze/intro-rust/mutability/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "b7fe49ef59ec05a339e046e1abdb500a", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Mutability\"\nengine: knitr\n---\n\nIn R, most objects are effectively immutable β€” when you modify one, R copies it \nfirst. This is called copy-on-write semantics. Rust takes a stricter approach, \nmaking variables immutable by default, and the compiler enforces this at compile \ntime. To make a variable mutable, you must declare it with the `mut` keyword:\n\n```rust\nlet mut x = 5;\nx = 6; // works because x is mutable\n```\n\nWithout `mut`, attempting to reassign a variable is a compile error. This helps \ncatch bugs early by making data changes explicit. Note that when updating a \nmutable variable, you do not re-bind it with `let` β€” you simply assign the new \nvalue directly.\n\nMutability is especially useful for accumulating values across a loop. In the \nprevious section, we saw that variables defined inside a loop cannot be accessed \noutside it. The solution is to declare a mutable variable in the outer scope and \nupdate it inside the loop:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let numbers = vec![1, 2, 3];\n let mut doubled = 0;\n\n for n in numbers {\n doubled = n * 2;\n println!(\"{} doubled is {}\", n, doubled);\n }\n\n // `doubled` is accessible here because it was declared outside the loop\n println!(\"Last doubled: {}\", doubled);\n}\n```\n\n```default\n1 doubled is 2\n2 doubled is 4\n3 doubled is 6\nLast doubled: 6\n``` \n\nYou can also use `+=` to add and assign in one step β€” `x += 10` is equivalent to\n`x = x + 10`. This makes computing running totals concise:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let x = vec![1.0, 2.0, 3.0, 4.0];\n let n = x.len() as f64;\n let mut total = 0.0;\n\n for xi in x {\n total += xi;\n }\n\n println!(\"The mean is: {}\", total / n);\n}\n```\n\n```default\nThe mean is: 2.5\n```\n\nLike scalar variables, vectors must also be declared `mut` to modify them after \ncreation. You can create an empty mutable vector and let Rust infer the data \ntype based on how you go on to use it:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let mut names = Vec::new();\n names.push(\"Alice\");\n names.push(\"Bob\");\n println!(\"{:?}\", names);\n}\n```\n\n```default\n[\"Alice\", \"Bob\"]\n```\n\nHere, `Vec::new()` creates an empty vector, and `.push()` appends an element to \nthe end. Because the element appended is a character string, the Rust compiler \nis able to infer the type of the names vector at compile time. With this \nexample, we have also introduced another method, namely `.push()`, which is \nparticularly useful, but there are, in fact, many ways to modify mutable vectors \nin Rust. Let us mention just three here.\n\n**Clear** -- Instead of adding elements, you can remove all elements at once \nwith `.clear()`:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let mut nums = vec![1, 2, 3];\n nums.clear();\n println!(\"{:?}\", nums);\n}\n```\n\n```default\n[]\n```\n\n**Sort** -- Vectors can be sorted in place with `.sort()`. Not all types support \nordering, but numeric types and strings do:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let mut x = vec![11, 3, 7, 10, 1];\n println!(\"x before sorting: {x:?}\");\n x.sort();\n println!(\"x after sorting: {x:?}\");\n}\n```\n\n```default\nx before sorting: [11, 3, 7, 10, 1]\nx after sorting: [1, 3, 7, 10, 11]\n```\n\n**Extend** -- To combine two vectors, use `.extend()`, which appends the \ncontents of one vector onto another: \n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let mut a = vec![1, 2];\n let b = vec![3, 4];\n a.extend(b);\n println!(\"{:?}\", a);\n}\n```\n\n```default\n[1, 2, 3, 4]\n```\n\nNote that the vector being extended must be `mut` and that the second vector is \nmoved into the first β€” it can no longer be used after the call. We will \nelaborate on that last point more in the section on Rust's memory model, \nspecifically the concept of ownership.", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/ownership/execute-results/html.json b/_freeze/intro-rust/ownership/execute-results/html.json new file mode 100644 index 0000000..5f8d1bf --- /dev/null +++ b/_freeze/intro-rust/ownership/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "3037bf525cc3ad58f03f1c8f8930a9ed", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Ownership\"\nengine: knitr\n---\n\nRust is notorious for its **borrow checker**, the mechanism that enforces memory \nsafety at compile time. The core rule is that every value has a single owner, \nand once that ownership is transferred, the original variable can no longer be \nused. This is called a **move**. A move occurs whenever a variable is passed to \na function or assigned to another variable. After the move, the original binding \nis invalid. Consider this example from The Book:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let s1 = String::from(\"hello\");\n let s2 = s1;\n println!(\"{s1}, world!\");\n}\n```\n\nRunning this code will cause the following error:\n\n```default\nerror[E0382]: borrow of moved value: `s1`\n --> src/main.rs:4:16\n |\n2 | let s1 = String::from(\"hello\");\n | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait\n3 | let s2 = s1;\n | -- value moved here\n4 | println!(\"{s1}, world!\");\n | ^^ value borrowed here after move\n |\n = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)\nhelp: consider cloning the value if the performance cost is acceptable\n |\n3 | let s2 = s1.clone();\n | ++++++++\n\n\nFor more information about this error, try `rustc --explain E0382`.\n```\n\nThere is a lot of output here, but let us focus on the source of the error for \nnow. First, we see that `let s2 = s1;` moves the value in `s1` to `s2`, and then\nwe are told that the `println!` macro mistakenly seeks to borrow the value that \nhas been moved. Since that value is no longer *owned* by `s1`, it causes an \nerror. \n\n## Cloning\n\nIn the example above, we are also given helpful advice about how to fix the \nerror using `.clone()`, which copies the value rather than moving it. Cloning is \nprobably the simplest way to reuse the original value, as almost everything in \nRust can be cloned.\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let s1 = String::from(\"hello\");\n let s2 = s1.clone();\n println!(\"{s1}, world!\");\n}\n```\n\n```default\nhello, world!\n```\n\n## Functions \n\nThe above example also highlights that the `println` macro seeks to *borrow* the \nvalue in `s1`. In fact, any time you pass a variable to a function, its value is \nmoved, meaning the originally defined variable can no longer be used to access \nthe value outside of the function. For example, let's define a `mean()` \nfunction:\n\n```rust\nfn mean(x: Vec) -> f64 {\n let mut total = 0.0;\n let n = x.len();\n \n for xi in x {\n total += xi;\n }\n \n total / (n as f64)\n}\n```\n\nCalling this function twice on the same vector will fail because the first call \nmoves `x` into the function:\n\n```rust\nlet x = vec![0.0, 3.14, 10.1, 44.8];\n\nlet avg1 = mean(x); // ⬅️ x moved here!\nlet avg2 = mean(x); // ❌ compiler error\n```\n\nAs we just saw, one way of handling this is to clone the data before passing it\nto the function.\n\n```rust\nlet x = vec![0.0, 3.14, 10.1, 44.8];\n\nlet avg1 = mean(x.clone()); // ⬅️ x cloned here!\nlet avg2 = mean(x); // βœ… compiler happy\n```\n\nWhile cloning in this way is safe and correct, it has the unfortunate downside \nof allocating a new copy of the data each time β€” not an issue for small vectors\nlike `x`, but as they grow, finding a better approach will become more urgent.\n\n## Borrowing \n\nFortunately, there is a more efficient approach known as *borrowing*, which \ninvolves passing a reference to a value rather than moving it. In Rust, we \nsignal a reference by placing an ampersand `&` in front of a variable or type \nname, for example, `&x` or `&Vec`. These tell the compiler that we are \nborrowing a value rather than taking ownership. To use the `mean()`function with \na reference, notice that we have to update its signature to make the reference \ntype explicit:\n\n```rust\nfn mean(x: &Vec) -> f64 {\n let mut total = 0.0;\n let n = x.len();\n \n for xi in x {\n total += *xi;\n }\n \n total / (n as f64)\n}\n\nfn main() {\n let x = vec![1.0, 2.0, 3.0];\n let avg = mean(&x); // πŸ‘ˆ borrowing `x`\n println!(\"x is still usable: {:?}\", x);\n}\n```\n\n```default\nx is still usable: [1.0, 2.0, 3.0]\n```\n\nAn important restriction on referencing is that borrowed values cannot be moved \nor mutated β€” only read. You can borrow a value as many times as you like, you \njust cannot change it.\n\n## Slices\n\nRather than reference the entire array or vector, we can reference a slice, or a \ncontiguous sequence of elements within it. These are denoted in Rust using `&[T]` \nwhere `T` stands for a generic type. For example, `&[f64]` means a reference \nslice of 64-bit floating points. If we want to demonstrate the use of reference \nslices with our `mean()` function, we will as before have to update its \nsignature to make that type explicit:\n\n```{.rust filename=\"src/main.rs\"} \nfn mean(x: &[f64]) -> f64 {\n let mut total = 0.0;\n let n = x.len();\n \n for xi in x {\n total += *xi;\n }\n \n total / (n as f64)\n}\n\nfn main() {\n let x = [0.0, 20.0, 742.3]; // array\n let y = vec![1.0, 2.0, 3.0]; // vector\n\n println!(\"the mean of x is: {}\", mean(&x));\n println!(\"the mean of y is: {}\", mean(&y));\n}\n```\n\n```default\nthe mean of x is: 254.1\nthe mean of y is: 2\n```\n\nThere are two things to note about this example. The first is very subtle. We \nupdated the signature for mean to take `x: &[f64]`, a slice. However, we passed \n`&x` and `&y` to the function, which are references to arrays and vectors. What \nis going on here? The answer is an implicit conversion. Both arrays and vectors\nhave methods for converting their generic references to slices, and those are\ninvoked when passing them into a function with this signature.\n\nFollowing from that, the second thing to note is that a function written to \naccept `&Vec` works only on vectors. That is because there is no method\nfor converting array references to vector references. But as we just saw, both \ncan be converted to slices. Rewriting a function to accept `&[f64]`, thus, makes \nit more general at no cost. So, in practice, we should prefer slices over vector \nreferences whenever the function only needs to read the data β€” it is more \nflexible and equally efficient.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/structures/execute-results/html.json b/_freeze/intro-rust/structures/execute-results/html.json new file mode 100644 index 0000000..9ddae7e --- /dev/null +++ b/_freeze/intro-rust/structures/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "88dbb7412fdc7652681b1523934a4bff", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Structures\"\nengine: knitr\n---\n\nRust lets you define custom types by grouping related values together using the \n`struct` keyword. This is similar in spirit to a named list in R, but with fixed \nfields and a declared type. Struct names use PascalCase, while field names use \nsnake_case:\n\n```rust\nstruct Person {\n name: String,\n age: i32,\n}\n```\n\nA struct is created using \"literal\" syntax, where we write the struct name \nfollowed by curly brackets containing each field and its value:\n\n```rust\nlet me = Person { name: \"Josiah\".to_string(), age: 29 };\n```\n\n## Derive\n\nBy default, a new struct has no built-in behavior. It does not even have a \ndefined behavior for printing. So, right now, if you try to print the `Person` \nstruct, you will get an error.\n\n```{.rust filename=\"src/main.rs\"} \nstruct Person {\n name: String,\n age: i32\n}\n\nfn main() {\n let me = Person { name: \"Josiah\".to_string(), age: 29 };\n println!(\"{me:?}\");\n}\n```\n\n```default\nerror[E0277]: `Person` doesn't implement `Debug`\n --> src/main.rs:8:15\n |\n8 | println!(\"{me:?}\");\n | ^^^^^^ `Person` cannot be formatted using `{:?}` because it doesn't implement `Debug`\n |\n = help: the trait `Debug` is not implemented for `Person`\n = note: add `#[derive(Debug)]` to `Person` or manually `impl Debug for Person`\n = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)\nhelp: consider annotating `Person` with `#[derive(Debug)]`\n |\n1 + #[derive(Debug)]\n2 | struct Person {\n |\n\nFor more information about this error, try `rustc --explain E0277`.\n```\n\nHere again we see a great demonstration of Rust's very informative error \nmessages. In this case, it also suggests a solution. We should derive the \n`Debug` trait. What is a trait exactly? In Rust, traits are similar to R's S3 \ngenerics β€” they define a set of methods that a type can support. In this case, \nthe `Debug` trait allows for printing the content of a struct to stdout using \nthe special `:?` syntax in `println!()` or the `dbg!()` macro. \n\nFor very simple behaviors like printing, it is possible to derive the behavior \nautomatically from the struct definition itself. In fact, this is what the error \nmessage above is telling us to do. It tells us to use another type of macro, \nspecifically the `derive` macro, placing it above the struct definition and \ntelling it what specific behavior to derive, in this case, `Debug`.\n\n```{.rust filename=\"src/main.rs\"} \n#[derive(Debug)]\nstruct Person {\n name: String,\n age: i32\n}\n\nfn main() {\n let me = Person { name: \"Josiah\".to_string(), age: 29 };\n println!(\"{me:?}\");\n}\n```\n\n```default\nPerson { name: \"Josiah\", age: 29 }\n```\n\n## Fields \n\nFields in a struct can be accessed with dot notation: `me.name`, `me.age`. But \nbeware that ownership rules apply here, too. Accessing a field by moving it out \nof the struct partially moves the struct itself, making the whole struct \nunusable afterward:\n\n```{.rust filename=\"src/main.rs\"} \n#[derive(Debug)]\nstruct Person {\n name: String,\n age: i32,\n}\n\nfn main() {\n let me = Person { name: \"Josiah\".to_string(), age: 29 };\n let name = me.name; // ⬅️ name moved out\n println!(\"{me:?}\"); // ❌ error: partial move\n}\n```\n\n```default\nerror[E0382]: borrow of partially moved value: `me`\n --> src/main.rs:10:16\n |\n 9 | let name = me.name; // name moved out\n | ------- value partially moved here\n10 | println!(\"{me:?}\"); // error: partial move\n | ^^ value borrowed here after partial move\n |\n = note: partial move occurs because `me.name` has type `String`, which does not implement the `Copy` trait\n = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)\n\nFor more information about this error, try `rustc --explain E0382`.\n```\n\nTo read a field without consuming it, borrow it instead by referencing the \nstruct.\n\n```{.rust filename=\"src/main.rs\"} \n#[derive(Debug)]\nstruct Person {\n name: String,\n age: i32,\n}\n\nfn main() {\n let me = Person { name: \"Josiah\".to_string(), age: 29 };\n let name = &me.name;\n println!(\"{me:?}\");\n}\n```\n\n```default\nPerson { name: \"Josiah\", age: 29 }\n```\n\n## Destructuring\n\nRust lets you unpack all fields of a struct in a single `let` binding. This is \ncalled destructuring and uses the syntax `let StructName { field1, field2 } = variable`:\n\n```{.rust filename=\"src/main.rs\"} \nfn main() {\n let me = Person { name: \"Josiah\".to_string(), age: 29 };\n let Person { name, age } = me;\n println!(\"{name} is {age} years old\");\n}\n```\n\n```default\nJosiah is 29 years old\n```\n\nDestructuring is useful when you need several fields at once and want to avoid\nrepeated `.field` access. The struct is consumed in the process, so the original \nvariable is no longer available.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/intro-rust/types/execute-results/html.json b/_freeze/intro-rust/types/execute-results/html.json new file mode 100644 index 0000000..64ac31e --- /dev/null +++ b/_freeze/intro-rust/types/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "8025007c7e286f43872d1bda0692bb8c", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Basic Types\"\nengine: knitr\n---\n\nIn R, everything is a vector or a *collection* of values. There is no concept of\na single value variable or scalar. The closest equivalent to that is a \nlength-one vector. In Rust, however, scalars are the building blocks of \neverything. When a collection is needed, it is made directly from scalars. For \nthis reason, you will often hear them referred to as **primitives**. A \nprimitive or scalar can be used to hold a single value of a specific data type, \nincluding strings, integers, and floats, among others. In this tutorial, we will \ncover some of those data types, focusing on how to instantiate them and convert\nbetween them.\n\nIntegers in Rust are a good place to start. They can be either signed, meaning \nthey can represent negative values, or unsigned, meaning they support only \npositive values. Both varieties come in several sizes depending on how many bits \nare needed to store the value:\n\n- Signed integers: `i8`, `i16`, `i32`, `i64`, `i128`\n- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`\n\nThe letter prefix indicates the type (`i` for signed, `u` for unsigned) and the \nnumber indicates the bit width. Unsigned integers can hold larger positive \nvalues than their signed counterparts because they do not need to use a bit to \nrepresent a negative value:\n\n``` rust\ni32::MAX // 2147483647\nu32::MAX // 4294967295\n```\n\nFloating points are similar to integers but can represent decimal values, too.\nUnlike integers, floating points are also always signed and come in two sizes: \n`f32` and `f64`. \n\n::: {.callout-note}\nIn R, integer vectors are comprised of only `i32` values, and floating points, \ncalled doubles, are always `f64` values.\n:::\n\nRust can usually infer types. For instance, it can distinguish the integer `1` \nfrom the floating point `1.0`, but you can also specify the type explicitly. \nThere are two ways to do this β€” using `:` in the assignment, or by appending a \ntype suffix to the literal value:\n\n``` rust\nlet x: f64 = 10.0;\nlet x = 10.0f64;\n```\n\nYou can also use `_` as a visual separator when writing numeric literals. The\nfollowing are all identical:\n\n``` rust\nlet x: i32 = 1000;\nlet x = 1000i32;\nlet x = 1_000_i32;\n```\n\nThis pattern of explicitly declaring the type of a variable may seem somewhat \nverbose to the average R user since R is very lax about types, relying as it \ndoes on a lot of implicit conversions. Becuase of Rust's memory management \nmodel, however, Rust is a strongly, statically typed language. That means that \nall types must be known when you go to build or compile your crate. It also \nprecludes running operations on different types. For example, math expressions \ncan only be performed between values of the same type, so you cannot, for \nexample, directly add an `f64` and an `i32`. You must first cast one to match \nthe type of the other, in the simplest case using the `as` keyword:\n\n``` rust\nfn add2(x: f64, y: i32) -> f64 {\n x + (y as f64)\n}\n```\n\nIf you call `add2(3.2, 2)`, the function will first cast `y` to `f64` before\ndoing addition, returning the `f64` value `5.2`. Before we continue, note that \nRust supports all the same numeric operators as R: `+`, `-`, `/`, `*`, and `%` \nfor remainder (equivalent to `%%` in R).", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/extendr-macro/execute-results/html.json b/_freeze/user-guide/extendr-macro/execute-results/html.json new file mode 100644 index 0000000..5c1ba96 --- /dev/null +++ b/_freeze/user-guide/extendr-macro/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "5a0471ae02813c293ee464427b6f0104", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"The `#[extendr]` macro\"\nexecute: \n eval: false\n---\n\nThe central task of extendr is to make Rust code available in R. Crucial to this\neffort is the attribute macro, `#[extendr]`. This chapter provides a brief \nintroduction to the use of that macro. \n\n::: {.callout-note}\nIf you would like to learn more about Rust macros, check The Book, specifically \n[Part 5 of Ch. 20](https://doc.rust-lang.org/book/ch20-05-macros.html). You may \nalso want to consult the section on [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html)\nin the official Rust reference manual.\n:::\n\n\n\nRust allows attribute macros to be placed on functions, struct, enums, and \nimpls, and extendr supports all of these. The general pattern is mostly the same\nin each case, but for starters, let's consider how to apply the extendr macro to\na Rust function in order to make it available in R. In order for that to happen, \nthree things need to happen. First, you need to declare the extendr namespace to \nthe compiler with `use`. After that, you need to annotate whatever function you \nwant to make available in R with the `#[extendr]` macro. Finally, you must \nregister the function in the `extendr_module! {}` macro. \n\nAs an example, suppose that you have created a function that gives the answer to \nthe question \"What is the meaning of life?\" and that you have very thoughtfully \ndecided to share this wisdom with the R community. Your main Rust library script \nshould then look like this:\n\n```{.rust filename=\"src/lib.rs\"}\nuse extendr_api::prelude::*;\n\n#[extendr]\nfn answer_to_life() -> i32 {\n 42\n}\n\nextendr_module! {\n mod hitchhiker;\n fn answer_to_life;\n}\n```\n\nHere, the `#[extendr]` macro is placed directly above the function definition.\n\n\n## `ToVectorValue` trait\n\nIn order for an item to be returned from a function marked with the `#[extendr]`\nattribute macro, it must be able to be turned into an R object. In extendr, the\nstruct `Robj` is a catch all for any type of R object.\n\n::: callout-note\nFor those familiar with PyO3, the `Robj` struct is similar in concept to the\n[`PyAny`](https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html) struct.\n:::\n\nThe `ToVectorValue` trait is what is used to convert Rust items into R objects.\nThe trait is implemented on a number of standard Rust types such as `i32`,\n`f64`, `usize`, `String` and more (see [all foreign implementations\nhere](https://extendr.github.io/extendr/extendr_api/robj/into_robj/trait.ToVectorValue.html#foreign-impls))\nwhich enables these functions to be returned from a Rust function marked with\n`#[extendr]`.\n\n::: callout-note\nIn essence, all items that are returned from a function must be able to be\nturned into an `Robj`. Other extendr types such as `List`, for example, have a\n`From for Robj` implementation that defines how it is converted into an\n`Robj`.\n:::\n\nThis means that with a little extra work, the `Shape` enum can be returned to R.\nTo do so, the `#[extendr]` macro needs to be added to an impl block.\n\n## Exporting `impl` blocks\n\nThe other supported item that can be made available to R is an\n[`impl`](https://doc.rust-lang.org/std/keyword.impl.html) block. `impl` is a\nkeyword that allows you to *implement* a trait or an inherent implementation.\nThe `#[extendr]` macro works with inherent implementations. These are `impl`s on\na type such as an `enum` or a `struct`. extendr *does not* support using\n`#[extendr]` on trait impls.\n\n::: callout-note\nYou can only add an inherent implementation on a type that you have own and not\nprovided by a third party crate. This would violate the [orphan\nrules](https://github.com/Ixrec/rust-orphan-rules?tab=readme-ov-file#what-are-the-orphan-rules).\n:::\n\nContinuing with the `Shape` example, this enum alone cannot be returned to R.\nFor example, the following code will result in a compilation error\n\n```rust\n#[derive(Debug)]\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\n#[extendr]\nfn make_shape(shape: &str) -> Shape {\n match shape {\n \"triangle\" => Shape::Triangle,\n \"rectangle\" => Shape::Rectangle,\n \"pentagon\" => Shape::Pentagon,\n \"hexagon\" => Shape::Hexagon,\n &_ => unimplemented!()\n }\n}\n```\n\n``` \nerror[E0277]: the trait bound `Shape: ToVectorValue` is not satisfied\n --> src/lib.rs:19:1\n |\n19 | #[extendr]\n | ^^^^^^^^^^ the trait `ToVectorValue` is not implemented for `Shape`, which is required by `extendr_api::Robj: From`\n |\n```\n\nHowever, you add the `#[extendr]` attribute to the `Shape` enum, it can be\nreturned to R.\n\n\n::: {.cell}\n\n```{.extendrsrc .cell-code}\n#[derive(Debug)]\n#[extendr]\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\n#[extendr]\nfn make_shape(shape: &str) -> Shape {\n match shape {\n \"triangle\" => Shape::Triangle,\n \"rectangle\" => Shape::Rectangle,\n \"pentagon\" => Shape::Pentagon,\n \"hexagon\" => Shape::Hexagon,\n &_ => unimplemented!()\n }\n}\n\n```\n:::\n\n\nIt is also possible to add methods to structs/enums and their instances:\n\n\n::: {.cell}\n\n```{.extendrsrc .cell-code}\n#[derive(Debug)]\n#[extendr]\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\n#[extendr]\nimpl Shape {\n fn new(x: &str) -> Self {\n match x {\n \"triangle\" => Self::Triangle,\n \"rectangle\" => Self::Rectangle,\n \"pentagon\" => Self::Pentagon,\n \"hexagon\" => Self::Hexagon,\n &_ => unimplemented!(),\n }\n }\n\n fn n_coords(&self) -> usize {\n match &self {\n Shape::Triangle => 3,\n Shape::Rectangle => 4,\n Shape::Pentagon => 4,\n Shape::Hexagon => 5,\n }\n }\n}\n```\n:::\n\n\nIn this example two new methods are added to the `Shape` enum. The first `new()`\nis like the `make_shape()` function that was shown earlier: it takes a `&str`\nand returns an enum variant. Now that the enum has an `impl` block with\n`#[extendr]` attribute macro, it can be exported to R by inclusion in the\n`extendr_module! {}` macro.\n\n```rust\nextendr_module! {\n mod hellorust;\n impl Shape;\n}\n```\n\nDoing so creates an environment in your package called `Shape`. The environment\ncontains all of the methods that are available to you.\n\n::: callout-tip\nThere are use cases where you may not want to expose any methods but do want to\nmake it possible to return a struct or an enum to the R. You can do this by\nadding an empty impl block with the `#[extendr]` attribute macro.\n:::\n\nIf you run `as.list(Shape)` you will see that there are two functions in the\nenvironment which enable you to call the methods defined in the impl block. You\nmight think that this feel like an [R6\nobject](https://r6.r-lib.org/articles/Introduction.html) and you'd be right\nbecause an R6 object essentially is an environment!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nas.list(Shape)\n```\n:::\n\n\nCalling the `new()` method instantiates a new enum variant.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntri <- Shape$new(\"triangle\")\ntri\n```\n:::\n\n\nThe newly made `tri` object is an [external\npointer](https://cran.r-project.org/doc/manuals/R-exts.html#External-pointers-and-weak-references)\nto the `Shape` enum in Rust. This pointer has the same methods as the Shape\nenvironmentβ€”though they cannot be seen in the same way. For example you can run\nthe `n_coords()` method on the newly created object.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntri$n_coords()\n```\n:::\n\n\n::: callout-tip\nTo make the methods visible to the `Shape` class you can define a `.DollarNames`\nmethod which will allow you to preview the methods and attributes when using the\n`$` syntax. This is very handy to define when making an impl a core part of your\npackage.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n.DollarNames.Shape = function(env, pattern = \"\") {\n ls(Shape, pattern = pattern)\n}\n```\n:::\n\n:::\n\n### `impl` ownership\n\nAdding the `#[extendr]` macro to an impl allows the struct or enum to be made\navailable to R as an external pointer. Once you create an external pointer, that\nis then owned by R. So you can only get references to it or mutable references.\nIf you need an owned version of the type, then you will need to clone it.\n\n## Accessing exported `impl`s from Rust\n\nInvariably, if you have made an impl available to R via the `#[extendr]` macro,\nyou may want to define functions that take the impl as a function argument.\n\nDue to R owning the `impl`'s external pointer, these functions cannot take an\nowned version of the impl as an input. For example trying to define a function\nthat subtracts an integer from the `n_coords()` output like below returns a\ncompiler error.\n\n```rust\n#[extendr]\nfn subtract_coord(x: Shape, n: i32) -> i32 {\n (x.n_coords() as i32) - n\n}\n```\n\n``` \nthe trait bound `Shape: extendr_api::FromRobj<'_>` is not satisfied\n --> src/lib.rs:53:22\n |\n | fn subtract_coord(x: Shape, n: i32) -> i32 {\n | ^^^^^ the trait `extendr_api::FromRobj<'_>` is not implemented for `Shape`\n |\nhelp: consider borrowing here\n |\n | fn subtract_coord(x: &Shape, n: i32) -> i32 {\n | +\n | fn subtract_coord(x: &mut Shape, n: i32) -> i32 {\n | ++++\n```\n\nAs most often, the compiler's suggestion is a good one. Use `&Shape` to use a\nreference.\n\n## `ExternalPtr`: returning arbitrary Rust types\n\nIn the event that you need to return a Rust type to R that doesn't have a\ncompatible impl or is a type that you don't own, you can use `ExternalPtr`.\nThe `ExternalPtr` struct allows any item to be captured as a pointer and\nreturned to R.\n\nHere, for example, an `ExternalPtr` is returned from the `shape_ptr()`\nfunction.\n\n::: callout-tip\nAnything that is wrapped in `ExternalPtr` must implement the `Debug` trait.\n:::\n\n\n::: {.cell}\n\n```{.extendrsrc .cell-code}\n#[derive(Debug)]\nenum Shape {\n Triangle,\n Rectangle,\n Pentagon,\n Hexagon,\n}\n\n#[extendr]\nfn shape_ptr(shape: &str) -> ExternalPtr {\n let variant = match shape {\n \"triangle\" => Shape::Triangle,\n \"rectangle\" => Shape::Rectangle,\n \"pentagon\" => Shape::Pentagon,\n \"hexagon\" => Shape::Hexagon,\n &_ => unimplemented!(),\n };\n\n ExternalPtr::new(variant)\n}\n```\n:::\n\n\nUsing an external pointer, however, is far more limiting than the `impl` block.\nFor example, you cannot access any of its methods.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntri_ptr <- shape_ptr(\"triangle\")\ntri_ptr$n_coords()\n```\n:::\n\n\nTo use an `ExternalPtr`, you have to go through a bit of extra work for it.\n\n\n\n```rust\n#[extendr]\nfn n_coords_ptr(x: Robj) -> i32 {\n let shape = TryInto::>::try_into(x); \n \n match shape {\n Ok(shp) => shp.n_coords() as i32,\n Err(_) => 0\n }\n}\n```\n\nThis function definition takes an `Robj` and from it, tries to create an\n`ExternalPtr`. Then, if the conversion did not error, it returns the\nnumber of coordinates as an `i32` (R's version of an integer) and if there was\nan error converting, it returns 0.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntri_ptr <- shape_ptr(\"triangle\")\n\nn_coords_ptr(tri_ptr)\n\nn_coords_ptr(list())\n```\n:::\n\n\nFor a good example of using `ExternalPtr` within an R package, refer to the\n[`b64` R package](https://github.com/extendr/b64).", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file From 5ee978e690580033f1b137e417d80ed3174b8427 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Mon, 16 Mar 2026 13:08:12 -0600 Subject: [PATCH 16/20] terminal vibes --- CLAUDE.md | 65 ++++++++++++ .../execute-results/html.json | 4 +- _quarto.yml | 11 +-- contributing/colors-and-fonts.qmd | 25 +++-- css/extendr.scss | 87 +++++++++------- css/index.css | 99 +++++++++++++++++++ index.qmd | 44 +++++---- 7 files changed, 264 insertions(+), 71 deletions(-) create mode 100644 CLAUDE.md create mode 100644 css/index.css diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b33a035 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,65 @@ +## Overview + +This repo contains code and data required to compile the **Quarto-based +documentation website** for the [extendr](https://github.com/extendr/extendr) +project β€” a framework for writing R packages using Rust. Published at +https://extendr.github.io. + +## Codebase + +**extendr** +version: 0.8.1 +repo: https://github.com/extendr/extendr +description: The core Rust library. Read source files there directly when you +need to understand extendr's API, types, macros, or implementation details. + +**rextendr** +version: 0.4.2.9000 (dev) +repo: https://github.com/extendr/rextendr +description: The R package that scaffolds extendr projects and generates R +wrappers. Read source files there directly when you need to understand +`rextendr::use_extendr()`, template generation, or the wrapper generation +architecture. + +## Repository Structure + +``` +. +β”œβ”€β”€ _quarto.yml # Site config: navbar, sidebars, theme +β”œβ”€β”€ index.qmd # Landing page (3 cards: Get Started, Examples, Community) +β”œβ”€β”€ get-started.qmd # 4-step install guide (Rust, R β‰₯ 4.2, rextendr, rust-analyzer) +β”œβ”€β”€ changelog.qmd # Dynamically fetches extendr CHANGELOG.md from GitHub +β”œβ”€β”€ user-guide/ # User guide +β”œβ”€β”€ intro-rust/ # Intro to Rust for R developers +β”œβ”€β”€ contributing/ # Contributor guide (style, colors, scaffolding) +β”œβ”€β”€ blog/ # Blog posts +β”œβ”€β”€ css/ # Custom SCSS (extendr.scss, _variables.scss, _bootswatch.scss) +β”œβ”€β”€ images/ # Logo files +└── _extensions/ # Quarto extensions +``` + +## Quarto (`_quarto.yml`) + +This is a Quarto website. The main site structure and metadata are defined in +`_quarto.yml`, including the navbar, sidebars, theme, and page execution +settings. When adding or reorganizing pages, `_quarto.yml` is the first place to +look. + +## Content Sections + +- Get Started (`get-started.qmd`): An installation guide for setting up extendr + and rextendr. +- User Guide (`user-guide/`): The main guide for R package development with + extendr. +- Rust Basics (`intro-rust/`): An opinionated introduction to Rust for R + developers, covering the concepts needed to use extendr effectively. +- Contributing (`contributing/`): Guidelines for contributing to the extendr + documentation, including writing style, branding, and scaffolding details. + + +## Claude Behavior + +- DO NOT BE OBSEQUIOUS WITH PRAISE. +- BE CONCISE. Only elaborate when that is requested. +- When editing markdown files, please keep line length to 80 characters. +- DO NOT EDIT CLAUDE.md WITHOUT ASKING PERMISSION. \ No newline at end of file diff --git a/_freeze/contributing/colors-and-fonts/execute-results/html.json b/_freeze/contributing/colors-and-fonts/execute-results/html.json index 9fa59c8..4ba75b1 100644 --- a/_freeze/contributing/colors-and-fonts/execute-results/html.json +++ b/_freeze/contributing/colors-and-fonts/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "b7798a47c2a140005aabfc4597f3a421", + "hash": "60612fe49dbceaa98f588ba2971331ee", "result": { "engine": "knitr", - "markdown": "---\ntitle: \"Colors and Fonts\"\nengine: knitr\n---\n\n```{=html}\n\n```\n\nOur colors and themes are inspired by the painfully 90's color choices of the R\nFoundation, particularly [its logo](https://www.r-project.org/logo/), and the \nRust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). \n\n## Colors\n\nWe have defined several utility classes to make using consistent colors easier.\nThese can be used to define background and text colors. They are also used to\ndefine semantic colors, which are applied automatically. \n\n### Background colors\n\n:::::: {.d-flex}\n::: {.bg-dark-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-4\n:::\n::: {.bg-dark-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-3\n:::\n::: {.bg-dark-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-2\n:::\n::: {.bg-dark-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-orange-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-4\n:::\n::: {.bg-orange-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-3\n:::\n::: {.bg-orange-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-2\n:::\n::: {.bg-orange-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-4\n:::\n::: {.bg-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-3\n:::\n::: {.bg-blue-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-2\n:::\n::: {.bg-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-silver-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-4\n:::\n::: {.bg-silver-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-3\n:::\n::: {.bg-silver-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-2\n:::\n::: {.bg-silver-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-teal-4 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-4\n:::\n::: {.bg-teal-3 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-3\n:::\n::: {.bg-teal-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-2\n:::\n::: {.bg-teal-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-r-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-2\n:::\n::: {.bg-r-blue-1 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-1\n:::\n::::::\n\n### Text colors\n\n[.text-dark-blue-4]{.text-dark-blue-4}, [.text-dark-blue-3]{.text-dark-blue-3}, [.text-dark-blue-2]{.text-dark-blue-2}, [.text-dark-blue-1]{.text-dark-blue-1}\n\n[.text-orange-4]{.text-orange-4}, [.text-orange-3]{.text-orange-3}, [.text-orange-2]{.text-orange-2}, [.text-orange-1]{.text-orange-1}\n\n[.text-blue-4]{.text-blue-4}, [.text-blue-3]{.text-blue-3}, [.text-blue-2]{.text-blue-2}, [.text-blue-1]{.text-blue-1}\n\n[.text-silver-4]{.text-silver-4}, [.text-silver-3]{.text-silver-3}, [.text-silver-2]{.text-silver-2}, [.text-silver-1]{.text-silver-1}\n\n[.text-teal-4]{.text-teal-4}, [.text-teal-3]{.text-teal-3}, [.text-teal-2]{.text-teal-2}, [.text-teal-1]{.text-teal-1}\n\n[.text-r-blue-2]{.text-r-blue-2}, [.text-r-blue-1]{.text-r-blue-1}\n\n### Semantic colors\n\n[Primary](#){.btn .btn-primary .me-1}\n[Secondary](#){.btn .btn-secondary .me-1}\n[Success](#){.btn .btn-success .me-1}\n[Danger](#){.btn .btn-danger .me-1}\n[Warning](#){.btn .btn-warning .me-1}\n[Info](#){.btn .btn-info .me-1}\n[Light](#){.btn .btn-light .me-1}\n[Dark](#){.btn .btn-dark .me-1}\n\nThese will also apply to [Quarto callouts](https://quarto.org/docs/authoring/callouts.html).\n\n::: callout-note\nNote that there are five types of callouts, including: `note`, `tip`, `warning`, \n`caution`, and `important`.\n:::\n\n::: callout-warning\nCallouts provide a simple way to attract attention, for example, to this warning.\n:::\n\n::: callout-important\nThe callout heading is provided by the callout type, with the expected heading \n(i.e., Note, Warning, Important, Tip, or Caution).\n:::\n\n::: callout-tip\n## Tip With Title\n\nThis is an example of a callout with a title. Providing a callout heading is \noptional.\n:::\n\n::: {.callout-caution collapse=\"true\"}\n## Caution: Expand To Learn About Collapse\n\nThis is an example of a 'collapsed' caution callout that can be expanded by the \nuser. You can use `collapse=\"true\"` to collapse it by default or `collapse=\"false\"` \nto make a collapsible callout that is expanded by default.\n:::\n\nAnd they have utility classes for text colors:\n\n[.text-primary]{.text-primary}, [.text-primary-emphasis]{.text-primary-emphasis}, \n[.text-secondary]{.text-secondary}, [.text-secondary-emphasis]{.text-secondary-emphasis}, \n[.text-success]{.text-success}, [.text-success-emphasis]{.text-success-emphasis}, \n[.text-danger]{.text-danger}, [.text-danger-emphasis]{.text-danger-emphasis}, \n[.text-warning]{.text-warning}, [.text-warning-emphasis]{.text-warning-emphasis}, \n[.text-info]{.text-info}, [.text-info-emphasis]{.text-info-emphasis}, \n[.text-light]{.text-light}, [.text-light-emphasis]{.text-light-emphasis}, \n[.text-dark]{.text-dark}, [.text-dark-emphasis]{.text-dark-emphasis}, \n[.text-body]{.text-body}, [.text-body-emphasis]{.text-body-emphasis}, \n[.text-body-secondary]{.text-body-secondary}, [.text-body-tertiary]{.text-body-tertiary}\n\n## Fonts\n\nWe follow the Rust Foundation in using Noto Sans for body and smaller texts and\nNoto Serif for headers. We have also implemented utility classes for these, \nthough they should not be required.\n\n:::::: {.d-flex .gap-3}\n::: {.bg-dark-blue-3 .text-white .font-serif .p-3 .flex-fill}\n[Noto Serif]{.fs-3} \n.font-serif \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n\n::: {.bg-silver-4 .font-sans .p-3 .flex-fill .text-auto-dark}\n[Noto Sans]{.fs-3} \n.font-sans \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n::::::\n\n## Light and Dark\n\nThe `.bg-*` and `.text-*` utility classes automatically adapt to light and dark\nmode by flipping the tint scale β€” so `.text-dark-blue-4` in light mode becomes \n`.text-dark-blue-1` in dark mode, and `.text-dark-blue-1` in light mode becomes \n`.text-dark-blue-4` in dark mode. This means you generally don't need to\nthink about dark mode when using these classes. If you need a color to stay \nfixed regardless of the theme, however, simply append `-fixed` to the class \nname: `.bg-orange-4-fixed` and `.text-orange-4-fixed` will always render the \noriginal color. \n\nHere are some examples. To see how they work, toggle light and dark mode in the \nbrowser:\n\n[.bg-orange-4]{.bg-orange-4} \n[.bg-orange-4-fixed]{.bg-orange-4-fixed}\n\nNotice that the font changes from black to white in each example. This is \nBootstrap's color system adapting to the page background, which is meant to \nensure a certain contrast level for accessibility. Semantic colors (`.text-primary`, \n`.btn-success`, callouts, etc.) are also handled by Bootstrap's color system and \ndo not need any special treatment. However, you will notice that Bootstrap's \ncolor system does not recognize these utilities - currently, it does not even \nrecognize its own utilities! - so contrast can be lost when using them. To \nensure that accessibility is maintained, you may want to add `.text-auto-dark` \nor `.text-auto-light` classes, too. These will ensure that white and black text \nare correctly used with our utilities.\n\n[.bg-orange-1 with .text-auto-light]{.bg-orange-1 .text-auto-light} \n[.bg-orange-4 with .text-auto-dark]{.bg-orange-4 .text-auto-dark} \n", + "markdown": "---\ntitle: \"Colors and Fonts\"\nengine: knitr\n---\n\n```{=html}\n\n```\n\nOur colors and themes are inspired by the delightfully awful 90's color choices \nof the R Foundation, particularly [its logo](https://www.r-project.org/logo/), \nand the Rust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). \n\n## Colors\n\nWe have defined several utility classes to make using consistent colors easier.\nThese can be used to define background and text colors. They are also used to\ndefine semantic colors, which are applied automatically. \n\n### Background colors\n\n:::::: {.d-flex}\n::: {.bg-dark-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-4\n:::\n::: {.bg-dark-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-3\n:::\n::: {.bg-dark-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-2\n:::\n::: {.bg-dark-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-dark-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-orange-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-4\n:::\n::: {.bg-orange-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-orange-3\n:::\n::: {.bg-orange-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-2\n:::\n::: {.bg-orange-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-orange-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-blue-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-4\n:::\n::: {.bg-blue-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-blue-3\n:::\n::: {.bg-blue-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-2\n:::\n::: {.bg-blue-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-blue-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-silver-4 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-4\n:::\n::: {.bg-silver-3 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-silver-3\n:::\n::: {.bg-silver-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-2\n:::\n::: {.bg-silver-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-silver-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-teal-4 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-4\n:::\n::: {.bg-teal-3 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-3\n:::\n::: {.bg-teal-2 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-2\n:::\n::: {.bg-teal-1 .color-swatch style=\"width: 160px; height: 40px;\"}\n.bg-teal-1\n:::\n::::::\n\n:::::: {.d-flex .mt-3}\n::: {.bg-r-blue-2 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-2\n:::\n::: {.bg-r-blue-1 .color-swatch .text-white style=\"width: 160px; height: 40px;\"}\n.bg-r-blue-1\n:::\n::::::\n\n### Text colors\n\n[.text-dark-blue-4]{.text-dark-blue-4}, [.text-dark-blue-3]{.text-dark-blue-3}, [.text-dark-blue-2]{.text-dark-blue-2}, [.text-dark-blue-1]{.text-dark-blue-1}\n\n[.text-orange-4]{.text-orange-4}, [.text-orange-3]{.text-orange-3}, [.text-orange-2]{.text-orange-2}, [.text-orange-1]{.text-orange-1}\n\n[.text-blue-4]{.text-blue-4}, [.text-blue-3]{.text-blue-3}, [.text-blue-2]{.text-blue-2}, [.text-blue-1]{.text-blue-1}\n\n[.text-silver-4]{.text-silver-4}, [.text-silver-3]{.text-silver-3}, [.text-silver-2]{.text-silver-2}, [.text-silver-1]{.text-silver-1}\n\n[.text-teal-4]{.text-teal-4}, [.text-teal-3]{.text-teal-3}, [.text-teal-2]{.text-teal-2}, [.text-teal-1]{.text-teal-1}\n\n[.text-r-blue-2]{.text-r-blue-2}, [.text-r-blue-1]{.text-r-blue-1}\n\n### Semantic colors\n\n[Primary](#){.btn .btn-primary .me-1}\n[Secondary](#){.btn .btn-secondary .me-1}\n[Success](#){.btn .btn-success .me-1}\n[Danger](#){.btn .btn-danger .me-1}\n[Warning](#){.btn .btn-warning .me-1}\n[Info](#){.btn .btn-info .me-1}\n[Light](#){.btn .btn-light .me-1}\n[Dark](#){.btn .btn-dark .me-1}\n\nThese will also apply to [Quarto callouts](https://quarto.org/docs/authoring/callouts.html).\n\n::: callout-note\nNote that there are five types of callouts, including: `note`, `tip`, `warning`, \n`caution`, and `important`.\n:::\n\n::: callout-warning\nCallouts provide a simple way to attract attention, for example, to this warning.\n:::\n\n::: callout-important\nThe callout heading is provided by the callout type, with the expected heading \n(i.e., Note, Warning, Important, Tip, or Caution).\n:::\n\n::: callout-tip\n## Tip With Title\n\nThis is an example of a callout with a title. Providing a callout heading is \noptional.\n:::\n\n::: {.callout-caution collapse=\"true\"}\n## Caution: Expand To Learn About Collapse\n\nThis is an example of a 'collapsed' caution callout that can be expanded by the \nuser. You can use `collapse=\"true\"` to collapse it by default or `collapse=\"false\"` \nto make a collapsible callout that is expanded by default.\n:::\n\nAnd they have utility classes for text colors:\n\n[.text-primary]{.text-primary}, [.text-primary-emphasis]{.text-primary-emphasis}, \n[.text-secondary]{.text-secondary}, [.text-secondary-emphasis]{.text-secondary-emphasis}, \n[.text-success]{.text-success}, [.text-success-emphasis]{.text-success-emphasis}, \n[.text-danger]{.text-danger}, [.text-danger-emphasis]{.text-danger-emphasis}, \n[.text-warning]{.text-warning}, [.text-warning-emphasis]{.text-warning-emphasis}, \n[.text-info]{.text-info}, [.text-info-emphasis]{.text-info-emphasis}, \n[.text-light]{.text-light}, [.text-light-emphasis]{.text-light-emphasis}, \n[.text-dark]{.text-dark}, [.text-dark-emphasis]{.text-dark-emphasis}, \n[.text-body]{.text-body}, [.text-body-emphasis]{.text-body-emphasis}, \n[.text-body-secondary]{.text-body-secondary}, [.text-body-tertiary]{.text-body-tertiary}\n\n## Fonts\n\nOur fonts are a mix of Rust Foundation fonts (Noto Sans and Serif) and IBM's \nPlex Mono. The monofont is typically used for headings and navbar, though Noto \nSerif may also be used. For body text, we use Noto Sans. Utility classes are \nprovided for each, though they should rarely be needed.\n\n:::::: {.d-flex .gap-2 .small}\n::: {.bg-dark-blue-4 .text-auto-dark .font-mono .p-3 .flex-fill}\n[IBM Plex Mono]{.fs-3} \n.font-mono \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n\n::: {.bg-dark-blue-3 .text-white .font-serif .p-3 .flex-fill}\n[Noto Serif]{.fs-3} \n.font-serif \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n\n::: {.bg-dark-blue-2 .text-auto-light .font-sans .p-3 .flex-fill}\n[Noto Sans]{.fs-3} \n.font-sans \nABCDEFGHIJKLMNOPQRSTUVWXYZ \nabcdefghijklmnopqrstuvwxyz \n0123456789\n:::\n::::::\n\n## Light and Dark\n\nThe `.bg-*` and `.text-*` utility classes automatically adapt to light and dark\nmode by flipping the tint scale β€” so `.text-dark-blue-4` in light mode becomes \n`.text-dark-blue-1` in dark mode, and `.text-dark-blue-1` in light mode becomes \n`.text-dark-blue-4` in dark mode. This means you generally don't need to\nthink about dark mode when using these classes. If you need a color to stay \nfixed regardless of the theme, however, simply append `-fixed` to the class \nname: `.bg-orange-4-fixed` and `.text-orange-4-fixed` will always render the \noriginal color. \n\nHere are some examples. To see how they work, toggle light and dark mode in the \nbrowser:\n\n[.bg-orange-4]{.bg-orange-4} \n[.bg-orange-4-fixed]{.bg-orange-4-fixed}\n\nNotice that the font changes from black to white in each example. This is \nBootstrap's color system adapting to the page background, which is meant to \nensure a certain contrast level for accessibility. Semantic colors (`.text-primary`, \n`.btn-success`, callouts, etc.) are also handled by Bootstrap's color system and \ndo not need any special treatment. However, you will notice that Bootstrap's \ncolor system does not recognize these utilities - currently, it does not even \nrecognize its own utilities! - so contrast can be lost when using them. To \nensure that accessibility is maintained, you may want to add `.text-auto-dark` \nor `.text-auto-light` classes, too. These will ensure that white and black text \nare correctly used with our utilities.\n\n[.bg-orange-1 with .text-auto-light]{.bg-orange-1 .text-auto-light} \n[.bg-orange-4 with .text-auto-dark]{.bg-orange-4 .text-auto-dark} \n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/_quarto.yml b/_quarto.yml index abd8436..600c45c 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -20,10 +20,10 @@ website: navbar: search: true - left: - - text: "Get Started" + right: + - text: "INSTALL" href: get-started.qmd - - text: Documentation + - text: DOCS menu: - text: "{extendr} crates" href: https://extendr.github.io/extendr/ @@ -35,9 +35,8 @@ website: href: user-guide/index.qmd - text: "Contributing" href: contributing/index.qmd - - text: "Blog" + - text: "NEWS" href: blog/index.qmd - right: - icon: github href: https://github.com/extendr/extendr - icon: discord @@ -94,8 +93,8 @@ format: html: html-table-processing: none theme: - dark: [darkly, css/extendr.scss] light: [flatly, css/extendr.scss] + dark: [darkly, css/extendr.scss] toc: true toc-location: right grid: diff --git a/contributing/colors-and-fonts.qmd b/contributing/colors-and-fonts.qmd index 27536e6..d2955e5 100644 --- a/contributing/colors-and-fonts.qmd +++ b/contributing/colors-and-fonts.qmd @@ -16,9 +16,9 @@ engine: knitr ``` -Our colors and themes are inspired by the painfully 90's color choices of the R -Foundation, particularly [its logo](https://www.r-project.org/logo/), and the -Rust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). +Our colors and themes are inspired by the delightfully awful 90's color choices +of the R Foundation, particularly [its logo](https://www.r-project.org/logo/), +and the Rust Foundation's official [brand guide](https://rustfoundation.org/brand-guide/). ## Colors @@ -183,11 +183,20 @@ And they have utility classes for text colors: ## Fonts -We follow the Rust Foundation in using Noto Sans for body and smaller texts and -Noto Serif for headers. We have also implemented utility classes for these, -though they should not be required. +Our fonts are a mix of Rust Foundation fonts (Noto Sans and Serif) and IBM's +Plex Mono. The monofont is typically used for headings and navbar, though Noto +Serif may also be used. For body text, we use Noto Sans. Utility classes are +provided for each, though they should rarely be needed. + +:::::: {.d-flex .gap-2 .small} +::: {.bg-dark-blue-4 .text-auto-dark .font-mono .p-3 .flex-fill} +[IBM Plex Mono]{.fs-3} +.font-mono +ABCDEFGHIJKLMNOPQRSTUVWXYZ +abcdefghijklmnopqrstuvwxyz +0123456789 +::: -:::::: {.d-flex .gap-3} ::: {.bg-dark-blue-3 .text-white .font-serif .p-3 .flex-fill} [Noto Serif]{.fs-3} .font-serif @@ -196,7 +205,7 @@ abcdefghijklmnopqrstuvwxyz 0123456789 ::: -::: {.bg-silver-4 .font-sans .p-3 .flex-fill .text-auto-dark} +::: {.bg-dark-blue-2 .text-auto-light .font-sans .p-3 .flex-fill} [Noto Sans]{.fs-3} .font-sans ABCDEFGHIJKLMNOPQRSTUVWXYZ diff --git a/css/extendr.scss b/css/extendr.scss index 324d376..31a4918 100644 --- a/css/extendr.scss +++ b/css/extendr.scss @@ -1,7 +1,6 @@ /*-- scss:uses --*/ @use "sass:color"; -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&display=swap'); /*-- scss:defaults --*/ $min-contrast-ratio: 4.5 !default; @@ -62,7 +61,7 @@ $secondary: $silver-4; $success: $teal-4; $danger: $orange-4; $warning: $flatly-yellow; -$info: $blue-4; +$info: $true-r-blue-1; $light: $gray-200; $dark: $gray-800; @@ -77,12 +76,14 @@ $link-color: color.mix(black, $teal-4, 20%, $method: oklch); $link-hover-color: color.mix(black, $teal-4, 40%, $method: oklch); $nav-link-color: $link-color; $nav-link-hover-color: $link-hover-color; -$navbar-bg: color.mix($gray-200, white, 20%, $method: oklch); +$navbar-bg: transparent; $navbar-fg: black; // fonts -$headings-font-family: "Noto Serif", Georgia, serif; +$font-family-mono: "IBM Plex Mono", "Courier New", monospace; +$font-family-serif: "Noto Serif", Georgia, serif; $font-family-sans-serif: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; +$headings-font-family: $font-family-mono; /*-- scss:rules --*/ @@ -104,6 +105,10 @@ $palette: ( --r-blue-1: #{$true-r-blue-1}; --r-blue-2: #{$true-r-blue-2}; + + --font-mono: #{$font-family-mono}; + --font-serif: #{$font-family-serif}; + --font-sans: #{$font-family-sans-serif}; } // generate utility classes using css custom properties @@ -128,19 +133,19 @@ $palette: ( } .bg-r-blue-1 { - background-color: var(--r-blue-1); + background-color: $true-r-blue-1; } .bg-r-blue-2 { - background-color: var(--r-blue-2); + background-color: $true-r-blue-2; } .text-r-blue-1 { - color: var(--r-blue-1); + color: $true-r-blue-1; } .text-r-blue-2 { - color: var(--r-blue-2); + color: $true-r-blue-2; } .font-sans { @@ -148,31 +153,50 @@ $palette: ( } .font-serif { - font-family: $headings-font-family; + font-family: $font-family-serif; } -.navbar { - max-width: 90%; - margin-left: auto; - margin-right: auto; - border-radius: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - - @media (min-width: 992px) { - max-width: 65%; - } +.font-mono { + font-family: $font-family-mono; +} - border: 1px solid $gray-200; +.navbar { + border-radius: 0; + font-family: $font-family-mono; .nav-link, .navbar-brand { color: black; } + .navbar-brand { + font-weight: 500; + font-size: 2rem; + } + .nav-link:hover { color: $dark-blue-4; } + + .dropdown-menu { + --bs-dropdown-link-color: #{$silver-4}; + } + + .navbar-container:has(#navbarCollapse) #quarto-search { + margin-left: 0; + } +} + +@media (max-width: 991px) { + #navbarCollapse .navbar-nav { + flex-direction: row; + flex-wrap: wrap; + + .nav-item:not(.compact) { + display: block; + width: 100%; + } + } } .sidebar-link.active { @@ -195,9 +219,6 @@ $palette: ( padding-bottom: 1rem; } -#homepage-header p { - margin-bottom: 0; -} .card { @@ -215,10 +236,6 @@ $palette: ( } } -#homepage-cards .card { - background-color: color.mix($gray-200, white, 20%, $method: oklch); - border: 2px solid $gray-200; -} .text-auto-dark { color: white; @@ -280,8 +297,8 @@ body.quarto-dark { } .navbar { - background-color: color.mix($gray-800, $gray-900, 50%, $method: oklch) !important; - border: 1px solid $gray-800; + background-color: transparent !important; + border: none; color: white; .nav-link, @@ -300,16 +317,16 @@ body.quarto-dark { .quarto-color-scheme-toggle .bi { filter: invert(1); } + + .dropdown-menu { + --bs-dropdown-link-color: #{$silver-1}; + } } .guide-header { border-bottom-color: $gray-700; } - #homepage-cards .card { - background-color: color.mix($gray-800, $gray-900, 50%, $method: oklch); - border: 2px solid $gray-800; - } .text-auto-dark { color: black; diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..23e655e --- /dev/null +++ b/css/index.css @@ -0,0 +1,99 @@ +#quarto-content { + padding-left: 1em; + padding-right: 1em; +} + +.hero-banner { + padding-left: 0.75rem; + padding-right: 0.75rem; + max-width: 100%; +} + + +#hero-header { + font-family: var(--font-mono); + color: var(--silver-4); +} + +#hero-header h1 { + font-family: var(--font-sans); + color: black; + font-weight: 500; +} + +body.quarto-dark #hero-header h1 { + color: white; +} + +#hero-header p { + font-size: 0.9rem; + margin-bottom: 0; +} + +@media (min-width: 992px) { + .hero-banner { + max-width: 50%; + } + + #hero-header p { + font-size: 1.1rem; + } + + #hero-header h1 { + font-size: 3.8rem; + } +} + +#hero-cli { + margin-top: 3em; +} + +/* tab buttons look like shell commands */ +#hero-cli .nav-tabs { + border-bottom: none; + gap: 0.25rem; +} + +#hero-cli .nav-tabs .nav-link { + font-family: var(--font-mono); + font-size: 0.9rem; + color: var(--silver-4); + background: none; + border: 1px solid transparent; + border-radius: 0; + padding: 0.25rem 0.5rem; +} + +#hero-cli .nav-tabs .nav-link::before { + content: "$ "; + color: var(--orange-4); +} + +#hero-cli .nav-tabs .nav-link:hover { + color: var(--dark-blue-4); + border-color: transparent; +} + +#hero-cli .nav-tabs .nav-link.active { + color: var(--dark-blue-4); + font-weight: 600; + background: none; + border-color: transparent; + border-bottom: 2px solid var(--orange-4); +} + +/* tab content looks like terminal output */ +#hero-cli .tab-content { + font-family: var(--font-mono); + font-size: 0.9rem; + background-color: var(--bs-gray-100); + border: none; + border-left: 3px solid var(--orange-4); + padding: 1rem 1.25rem; + margin-top: 0.5rem; +} + +/* dark mode */ +body.quarto-dark #hero-cli .tab-content { + background-color: var(--bs-gray-800); +} \ No newline at end of file diff --git a/index.qmd b/index.qmd index ff3d71b..866231e 100644 --- a/index.qmd +++ b/index.qmd @@ -1,18 +1,20 @@ --- title: "" toc: false -page-layout: full +page-layout: custom +css: css/index.css anchor-sections: false --- -:::::: {#homepage-header .text-center .w-75 .mx-auto} -[Extending R with Rust]{.fs-3 .text-silver-4} +::::::::: {.hero-banner} -:::{.fs-1 .mx-auto style="width: 80%;"} -Build blazingly fast R packages with developer-friendly and CRAN-ready tools. -::: +:::::: {#hero-header} +# Build blazingly fast R packages with Rust + +Use tools that put developers first. +Get support for publishing to CRAN. -::: {.mt-1} +::: {.mt-3} [![Github Actions Build Status](https://github.com/extendr/extendr/workflows/Tests/badge.svg)](https://github.com/extendr/extendr/actions) [![Crates.io](https://img.shields.io/crates/v/extendr-api.svg)](https://crates.io/crates/extendr-api) [![Documentation](https://docs.rs/extendr-api/badge.svg)](https://docs.rs/extendr-api) @@ -20,32 +22,34 @@ Build blazingly fast R packages with developer-friendly and CRAN-ready tools. ::: :::::: -:::: {#homepage-cards .grid style="margin-top: 4rem;"} -::: {.g-col-12 .g-col-md-4 .card .p-4} -### Get Started {{< iconify openmoji:rocket >}} +:::::: {#hero-cli} +::: {.panel-tabset} +## init +**Get Started** {{< iconify fluent-color:checkmark-circle-32 >}} Build your first Rust-powered R package in minutes. Follow our step-by-step guide or dive straight into a complete example. -[Installation β†’](/get-started.qmd) [β€’]{.text-silver-3} [User Guide β†’](/user-guide/index.qmd) -::: +β†’ [Installation](/get-started.qmd) +β†’ [User Guide](/user-guide/index.qmd) -::: {.g-col-12 .g-col-md-4 .card .p-4} -### See What's Possible {{< iconify openmoji:sparkler >}} +## run example +**See What's Possible** {{< iconify fluent-color:lightbulb-filament-32 >}} Explore R packages built with extendr. From high-performance data processing to system integrations, see what the community has built. -[Browse awesome-extendr β†’](https://github.com/extendr/awesome-extendr) -::: +β†’ [Browse awesome-extendr](https://github.com/extendr/awesome-extendr) -::: {.g-col-12 .g-col-md-4 .card .p-4} -### Join the Community {{< iconify fluent-color:people-interwoven-16 >}} +## --help +**Join the Community** {{< iconify fluent-color:people-interwoven-32 >}} extendr has a welcoming and active community of R and Rust developers. Come ask questions, share your work, or just hang out. -[Discord β†’](https://discord.gg/7hmApuc) [β€’]{.text-silver-3} [GitHub β†’](https://github.com/extendr/extendr) +β†’ [Discord](https://discord.gg/7hmApuc) +β†’ [GitHub](https://github.com/extendr/extendr) ::: -:::: +:::::: +::::::::: \ No newline at end of file From 018708d3b0ed88e2b342577270c34d914e0c00c5 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Tue, 17 Mar 2026 13:44:17 -0600 Subject: [PATCH 17/20] update CLAUDE --- CLAUDE.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b33a035..e989ed3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,13 +8,11 @@ https://extendr.github.io. ## Codebase **extendr** -version: 0.8.1 repo: https://github.com/extendr/extendr description: The core Rust library. Read source files there directly when you need to understand extendr's API, types, macros, or implementation details. **rextendr** -version: 0.4.2.9000 (dev) repo: https://github.com/extendr/rextendr description: The R package that scaffolds extendr projects and generates R wrappers. Read source files there directly when you need to understand @@ -33,8 +31,8 @@ architecture. β”œβ”€β”€ intro-rust/ # Intro to Rust for R developers β”œβ”€β”€ contributing/ # Contributor guide (style, colors, scaffolding) β”œβ”€β”€ blog/ # Blog posts -β”œβ”€β”€ css/ # Custom SCSS (extendr.scss, _variables.scss, _bootswatch.scss) -β”œβ”€β”€ images/ # Logo files +β”œβ”€β”€ css/ # Custom SCSS +β”œβ”€β”€ images/ # Image files └── _extensions/ # Quarto extensions ``` From 0acb9b51ab311d9e930a3ddf461f6cc4963c6683 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Tue, 17 Mar 2026 14:19:40 -0600 Subject: [PATCH 18/20] updates for review --- _quarto.yml | 5 +- contributing/index.qmd | 9 +- contributing/rextendr-internals.qmd | 110 +++++++ contributing/scaffolding.qmd | 451 ---------------------------- user-guide/package-structure.qmd | 5 +- 5 files changed, 120 insertions(+), 460 deletions(-) create mode 100644 contributing/rextendr-internals.qmd delete mode 100644 contributing/scaffolding.qmd diff --git a/_quarto.yml b/_quarto.yml index 600c45c..03eb475 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -86,8 +86,9 @@ website: - contributing/writing-code.qmd - contributing/documenting-code.qmd - contributing/testing-code.qmd - - contributing/scaffolding.qmd - - contributing/colors-and-fonts.qmd + - contributing/extendr-internals.qmd + - contributing/rextendr-internals.qmd + - contributing/colors-and-fonts.qmd format: html: diff --git a/contributing/index.qmd b/contributing/index.qmd index 179a701..962f6fe 100644 --- a/contributing/index.qmd +++ b/contributing/index.qmd @@ -10,14 +10,13 @@ date-format: long The extendr project has three main areas where you can contribute: **extendr crates** β€” The core Rust library. This is where the `#[extendr]` -macro, R type bindings, and the bulk of the Rust API live. +macro, R type bindings, and the Rust API live. Contributions here require Rust experience. The repository is at [github.com/extendr/extendr](https://github.com/extendr/extendr). -**rextendr package** β€” The R package that scaffolds new extendr projects -and generates R wrapper functions. Contributions here require R -package development experience. The repository is at -[github.com/extendr/rextendr](https://github.com/extendr/rextendr). +**rextendr package** β€” The R package that scaffolds new extendr projects. +Contributions here require R package development experience. The repository is +at [github.com/extendr/rextendr](https://github.com/extendr/rextendr). **Documentation website** β€” The site you are reading now. Contributions here range from fixing typos to writing new guides and tutorials. The diff --git a/contributing/rextendr-internals.qmd b/contributing/rextendr-internals.qmd new file mode 100644 index 0000000..6a6adba --- /dev/null +++ b/contributing/rextendr-internals.qmd @@ -0,0 +1,110 @@ +--- +title: "{rextendr} Internals" +--- + +This page describes various internal functions and processes used by rextendr to +setup and build R packages. It is designed to get contributors up-to-speed on +the internal mechanics of rextendr, so that they can more efficiently contribute +to development and maintenance. + +## Package Setup + +General R package setup is achieved with `usethis::create_package()`. +Scaffolding files required to call Rust code in R using extendr is then added to +the package directory with `rextendr::use_extendr()`. This involves +interpolating strings into template files in `inst/templates/`, which are then +written to the user's package directory via the internal function +`use_rextendr_template()`: + +```{.r filename="R/use_extendr.R"} +use_rextendr_template <- function( + template, # filename in inst/templates/ + save_as, # destination path in the user's package + data = list(), # named list of variables to interpolate into the template + quiet = FALSE, + overwrite = NULL +) +``` + +When `usethis` is available and `overwrite = NULL`, this delegates to +`usethis::use_template()`, which handles interactive prompting if the file +already exists. Otherwise it reads the template file directly, performs +interpolation, and writes the result. The templates directory currently includes +all of the following: + +```{r} +fs::dir_tree(system.file("templates", package = "rextendr")) +``` + +The exceptions to simple string interpolation are `Cargo.toml`, whose content is +generated programmatically before being written to file, and `Makevars.in` / +`Makevars.win.in`, which use a second layer of `@PLACEHOLDER@` substitution +performed at package build time by `tools/config.R`. + +You can see the content of these template files on main in the rextendr +repository here: {{< iconify bi:github >}} +[source](https://github.com/extendr/rextendr/tree/main/inst/templates). +For a description of what each generated file does, see the +[Project Structure](../user-guide/package-structure.qmd) page in the user guide. + +## Package Build + +When a developer calls `devtools::document()`, they initiate a set of steps to +build, document, and register functions in their R package. Here are those steps +in order: + +1. Check Rust and cargo versions +2. Generate `Makevars` from template +3. Compile the Rust crate into a static library +4. Generate R wrappers +5. Compile `entrypoint.c` and link the static library +6. Register exported routines with R on load +7. Clean up build artifacts + +These steps are driven by `R CMD INSTALL`, which `devtools::document()` calls +implicitly. + +### Setup + +The first thing that gets called is `configure` (`configure.win` on Windows). +The sole purpose of the configure script is to source the R script +`tools/config.R`, which in broad outline does two things. First, it runs +`tools/msrv.R` to check that the Rust version is consistent across metadata +files like `DESCRIPTION` and `Cargo.toml` (step 1). Second, it substitutes +`@PLACEHOLDER@` variables in the `Makevars.in` and `Makevars.win.in` templates +(signaled by `.in`) with relevant data to generate the actual Makevars file +(step 2). + +### Build + +The R package build process next invokes Makevars, which was just generated by +the configuration file. The Makevars in turn does two things. First, it calls +`cargo build` to compile the static library (step 3). Then it calls `cargo run` +to compile and execute the associated `document` binary, which generates all +extendr wrappers (basically `.Call()`) and writes them into +`R/extendr-wrappers.R` (step 4). Note that if a `vendor/` directory or +`rust/vendor.tar.xz` tarball is present, the Makevars also ensures that Cargo is +configured to use it for offline compilation β€” this is the essential mechanism +required for CRAN submissions. + +### Registeration + +Because we are building an R package with compiled code, R requires that we +register all routines via a C-level initialization function named +`R_init_`. This is where `entrypoint.c` comes in. Currently, extendr +generates its own version of this function β€” `R_init_{{{mod_name}}}_extendr` β€” +inside the Rust library. The C entrypoint then bridges the two, calling the +Rust-generated function through the R-facing `R_init_{{{mod_name}}}`. Compiling +`entrypoint.c` makes that symbol available on package load (step 5). When the +package next gets loaded, the C function is called to register compiled routines +(step 6). If you inspect `R/extendr-wrappers.R`, you will see where this +happens. The call is `@useDynLib(pkgname, .registration = TRUE)`. + +### Cleanup + +To prevent the `Makevars` generated by `configure` from being committed to git, +a `cleanup` is invoked (`cleanup.win` on Windows) that calls the shell command +`rm` to remove that file (step 7). + +Once these steps are taken, the compiled Rust should become available in the +current R session. diff --git a/contributing/scaffolding.qmd b/contributing/scaffolding.qmd deleted file mode 100644 index 366d1e6..0000000 --- a/contributing/scaffolding.qmd +++ /dev/null @@ -1,451 +0,0 @@ ---- -title: "Scaffolding" ---- - -This page describes how `rextendr::use_extendr()` generates its scaffolding, -intended for contributors who want to modify or add scaffolded files. For a -description of what each generated file does, see the -[Project Structure](../user-guide/package-structure.qmd) page in the user guide. -As noted there, the package directory should have the following structure: - -``` -. -β”œβ”€β”€ R -β”‚ └── extendr-wrappers.R -β”œβ”€β”€ cleanup -β”œβ”€β”€ cleanup.win -β”œβ”€β”€ configure -β”œβ”€β”€ configure.win -β”œβ”€β”€ src -β”‚ β”œβ”€β”€ .gitignore -β”‚ β”œβ”€β”€ Makevars.in -β”‚ β”œβ”€β”€ Makevars.win.in -β”‚ β”œβ”€β”€ entrypoint.c -β”‚ β”œβ”€β”€ -win.def -β”‚ └── rust -β”‚ β”œβ”€β”€ Cargo.toml -β”‚ └── src -β”‚ β”œβ”€β”€ document.rs -β”‚ └── lib.rs -└── tools - β”œβ”€β”€ config.R - └── msrv.R -``` - -## Template generation - -Nearly all files generated by `use_extendr()` come from templates stored in -`inst/templates/`. Each template is written to the user's package directory via -the internal function `use_rextendr_template()`: - -```{.r filename="R/use_extendr.R"} -use_rextendr_template <- function( - template, # filename in inst/templates/ - save_as, # destination path in the user's package - data = list(), # named list of variables to interpolate into the template - quiet = FALSE, - overwrite = NULL -) -``` - -When `usethis` is available and `overwrite = NULL`, this delegates to -`usethis::use_template()`, which handles interactive prompting if the file -already exists. Otherwise it reads the template file directly, performs -interpolation, and writes the result. - -### Variable interpolation - -By default, template variables use triple-brace syntax: `{{{variable_name}}}`. -Currently, interpolation is performed using `glue` with `.open = "{{{"` and -`.close = "}}}"`. Triple braces avoid conflicts with Rust's `{}` format syntax -and Makefile's `${}` variable syntax, both of which appear in some templates. - -The following variables are passed to templates, all derived from the user's -package name: - -| Variable | Description | Example | -|----------------------------|---------------------------------------------------|-------------| -| `{{{pkg_name}}}` | R package name, as-is | `hellorust` | -| `{{{mod_name}}}` | Package name sanitized to a valid Rust identifier | `hellorust` | -| `{{{lib_name}}}` | Rust library name | `hellorust` | -| `{{{cargo_toml_content}}}` | Full generated `Cargo.toml` content (see below) | β€” | - -If `NULL`, `mod_name` and `lib_name` are derived from `pkg_name`, which is -sanitized to ensure it is compliant with Rust naming conventions. They can also -be set independently via the `crate_name` and `lib_name` arguments to -`use_extendr()`. - -### The `inst/templates/` directory - -The templates directory currently includes all of the following: - -``` -inst/templates/ -β”œβ”€β”€ Cargo.toml -β”œβ”€β”€ Makevars.in -β”œβ”€β”€ Makevars.win.in -β”œβ”€β”€ _gitignore -β”œβ”€β”€ cleanup -β”œβ”€β”€ cleanup.win -β”œβ”€β”€ config.R -β”œβ”€β”€ configure -β”œβ”€β”€ configure.win -β”œβ”€β”€ document.rs -β”œβ”€β”€ entrypoint.c -β”œβ”€β”€ extendr-wrappers.R -β”œβ”€β”€ lib.rs -β”œβ”€β”€ msrv.R -β”œβ”€β”€ settings.json -└── win.def -``` - -Most templates are written verbatim or with simple string interpolation. The -exceptions are `Cargo.toml`, whose content is generated programmatically before -being passed to the template, and `Makevars.in` / `Makevars.win.in`, which use a -second layer of `@PLACEHOLDER@` substitution performed at package build time by -`tools/config.R`. - -## Rust source templates - -### `lib.rs` - -`lib.rs` is the entry point for the user's Rust library. The template provides -a minimal working example β€” a single `hello_world()` function β€” so the package -compiles and can be called from R immediately after scaffolding. - -```{.rust filename="inst/templates/lib.rs"} -use extendr_api::prelude::*; - -/// Return string `"Hello world!"` to R. -/// @export -#[extendr] -fn hello_world() -> &'static str { - "Hello world!" -} - -// Macro to generate exports. -// This ensures exported functions are registered with R. -// See corresponding C code in `entrypoint.c`. -extendr_module! { - mod {{{mod_name}}}; - fn hello_world; -} -``` - -The `extendr_module!` macro is the mechanism by which Rust functions, `impl` -blocks, and submodules are registered with R. The first entry must always be -`mod {{{mod_name}}}`, which declares the module name used internally by the -extendr runtime. Every additional entry β€” functions and `impl` blocks β€” must -be listed explicitly, or it will not be available in R. Users add their own -Rust functions to this file and register them in `extendr_module!`. - -### `document.rs` - -`document.rs` is a Rust binary that generates `R/extendr-wrappers.R`. It is -compiled alongside the library crate (registered as `[[bin]]` in `Cargo.toml`) -and run by `Makevars` via `cargo run --bin document` each time the package is -built. - -```{.rust filename="inst/templates/document.rs"} -// Generated by extendr: Do not edit by hand -fn main() -> Result<(), Box> { - let wrapper_path = "../R/extendr-wrappers.R"; - let header = "\ - # Generated by extendr: Do not edit by hand\n\ - # nolint start\n\ - \n\ - #' @usage NULL\n\ - #' @useDynLib {{{pkg_name}}}, .registration = TRUE\n\ - NULL\n\ - \n\ - "; - let footer = "# nolint end\n"; - let wrappers = {{{lib_name}}}::get_{{{mod_name}}}_metadata() - .make_r_wrappers(true, "{{{pkg_name}}}") - .map_err(|e| format!("failed to generate wrappers: {e}"))?; - std::fs::write(wrapper_path, format!("{header}{wrappers}{footer}")) - .map_err(|e| format!("failed to write {wrapper_path}: {e}"))?; - Ok(()) -} -``` - -The binary calls `get_{{{mod_name}}}_metadata()`, a function generated by the -`#[extendr]` macro on each exported item, which collects metadata about all -exported functions and their signatures. `make_r_wrappers()` then uses that -metadata to produce the corresponding R wrapper code. The result is written -directly to `R/extendr-wrappers.R`, relative to the `src/rust/` directory -where the binary runs. - -### `Cargo.toml` - -Unlike other templates, the content of `Cargo.toml` is constructed in R via -`to_toml()` before being passed to the template as the single variable -`{{{cargo_toml_content}}}`. This allows the crate name, lib name, edition, and -`extendr-api` version to be set dynamically based on the user's package and -rextendr's internal defaults. The resulting `Cargo.toml` for a package called -`hellorust` looks like this: - -```{.toml filename="src/rust/Cargo.toml"} -[package] -name = 'hellorust' -publish = false -version = '0.1.0' -edition = '2021' -rust-version = '1.65' - -[lib] -crate-type = ['rlib', 'staticlib'] -name = 'hellorust' - -[[bin]] -name = 'document' -path = 'document.rs' - -[dependencies] -extendr-api = '*' - -[profile.release] -lto = true -codegen-units = 1 -``` - -The crate is built as both an `rlib` and a `staticlib`. The `staticlib` is -linked into the R package's shared library. The `rlib` is needed by the -`document` binary, which must link against the library crate to introspect its -exported function metadata. The release profile enables LTO and single-codegen-unit -compilation to produce smaller, faster binaries β€” important for CRAN packages. - -## Build file templates - -### `entrypoint.c` - -R requires that compiled packages register their routines via a C-level -initialization function named `R_init_`. extendr generates its own -version of this function β€” `R_init_{{{mod_name}}}_extendr` β€” inside the Rust -library. The C file bridges the two: it declares the Rust-generated function -and calls it from the R-facing `R_init_{{{mod_name}}}`. - -```{.c filename="inst/templates/entrypoint.c"} -// We need to forward routine registration from C to Rust -// to avoid the linker removing the static library. - -void R_init_{{{mod_name}}}_extendr(void *dll); - -void R_init_{{{mod_name}}}(void *dll) { - R_init_{{{mod_name}}}_extendr(dll); -} -``` - -Without this forwarding, the linker may strip the static Rust library entirely -when building the R package shared object, since nothing in the C compilation -unit directly references it. - -### `Makevars.in` and `Makevars.win.in` - -These are the Makefile templates that drive the Rust compilation step. They are -not used directly by R β€” `tools/config.R` reads the appropriate template, -substitutes the `@PLACEHOLDER@` variables, and writes the final `src/Makevars` -or `src/Makevars.win` at package build time. - -```{.makefile filename="inst/templates/Makevars.in"} -TARGET_DIR = ./rust/target -LIBDIR = $(TARGET_DIR)/@LIBDIR@ -STATLIB = $(LIBDIR)/lib{{{lib_name}}}.a -PKG_LIBS = -L$(LIBDIR) -l{{{lib_name}}} - -all: $(SHLIB) rust_clean - -.PHONY: $(STATLIB) - -$(SHLIB): $(STATLIB) - -CARGOTMP = $(CURDIR)/.cargo -VENDOR_DIR = $(CURDIR)/vendor - -$(STATLIB): - - if [ -d ./vendor ]; then \ - echo "=== Using offline vendor directory ==="; \ - mkdir -p $(CARGOTMP) && \ - cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ - elif [ -f ./rust/vendor.tar.xz ]; then \ - echo "=== Using offline vendor tarball ==="; \ - tar xf rust/vendor.tar.xz && \ - mkdir -p $(CARGOTMP) && \ - cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ - fi - - export CARGO_HOME=$(CARGOTMP) && \ - export PATH="$(PATH):$(HOME)/.cargo/bin" && \ - @PANIC_EXPORTS@RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --lib @PROFILE@ --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ - - export CARGO_HOME=$(CARGOTMP) && \ - export PATH="$(PATH):$(HOME)/.cargo/bin" && \ - cargo run @CRAN_FLAGS@ --bin document --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ - - # Always clean up CARGOTMP - rm -Rf $(CARGOTMP); - -rust_clean: $(SHLIB) - rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ - -clean: - rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) $(VENDOR_DIR) -``` - -The build performs two `cargo` invocations: one to compile the static library -(`--lib`), and one to run the `document` binary that regenerates -`R/extendr-wrappers.R`. If a `vendor/` directory or `rust/vendor.tar.xz` -tarball is present, Cargo is configured to use it for offline compilation β€” -this is the mechanism used for CRAN submissions. - -The `@PLACEHOLDER@` variables are substituted by `tools/config.R`: - -| Placeholder | Description | -|-------------------|--------------------------------------------------------------------------------------------| -| `@LIBDIR@` | Path suffix to the compiled library, e.g. `release` or `wasm32-unknown-emscripten/release` | -| `@PROFILE@` | `--release` for release builds, empty for debug | -| `@CRAN_FLAGS@` | `-j 2 --offline` when building for CRAN with vendored deps, otherwise empty | -| `@TARGET@` | `--target=wasm32-unknown-emscripten` for WebR builds, otherwise empty | -| `@PANIC_EXPORTS@` | Sets `CARGO_PROFILE_*_PANIC=abort` env vars for WASM builds | -| `@CLEAN_TARGET@` | `$(TARGET_DIR)` on release builds, empty on debug builds | - -`Makevars.win.in` follows the same structure but targets the -`x86_64-pc-windows-gnu` toolchain and links additional Windows system -libraries (`ws2_32`, `advapi32`, `userenv`, `bcrypt`, `ntdll`). - -### `win.def` - -On Windows, the DLL export list must be specified explicitly. This file -declares the package's initialization routine as an export so that R can locate -it when loading the package. - -```{filename="inst/templates/win.def"} -EXPORTS -R_init_{{{mod_name}}} -``` - -### `_gitignore` - -Written to `src/.gitignore`. Excludes compiled artifacts, Cargo's working -directories, the vendor directory, and the generated `Makevars` files (which -are produced fresh at each build and should not be committed). - -```{filename="inst/templates/_gitignore"} -*.o -*.so -*.dll -target -.cargo -rust/vendor -Makevars -Makevars.win -``` - -The template is named `_gitignore` rather than `.gitignore` to prevent it from -being treated as a gitignore file within the rextendr repository itself. - -## Configuration scripts - -### `configure` and `configure.win` - -R runs `configure` (on Unix) or `configure.win` (on Windows) before compiling -a package. These scripts simply invoke `tools/config.R` via `Rscript`, which -does the real work of generating the final `Makevars` file. - -```{.bash filename="inst/templates/configure"} -#!/usr/bin/env sh -: "${R_HOME=`R RHOME`}" -"${R_HOME}/bin/Rscript" tools/config.R -``` - -```{.bash filename="inst/templates/configure.win"} -#!/usr/bin/env sh -"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/config.R -``` - -On Unix, `configure` must be executable. `use_extendr()` calls -`Sys.chmod("configure", "0755")` automatically after writing it. - -### `tools/msrv.R` - -This script reads the `SystemRequirements` field from `DESCRIPTION` to -determine the package's minimum supported Rust version (MSRV), then checks -that both `rustc` and `cargo` are installed and meet that requirement. It is -sourced by `tools/config.R` at the start of every build. - -The MSRV check works as follows: - -1. Confirms `SystemRequirements` contains both `cargo` and `rustc`. -2. Extracts the version string from the `rustc` entry, e.g. `rustc >= 1.65`. -3. Runs `rustc --version` and `cargo --version` on the system PATH (with - `~/.cargo/bin` appended). -4. Compares the installed `rustc` version against the MSRV using - `utils::compareVersion()`. Stops with an informative error if the installed - version is too old. -5. Prints the detected `cargo` and `rustc` versions to the build log β€” a - requirement for CRAN submissions. - -### `tools/config.R` - -`config.R` is the main build configuration script. It sources `tools/msrv.R`, -determines the correct build flags for the current context, and writes the -final `Makevars` file by substituting the `@PLACEHOLDER@` variables in the -appropriate `.in` template. The build context is determined by three environment -variables: - -| Variable | Effect | -|----------------------|------------------------------------------------------------------| -| `DEBUG` | Builds with `--debug` instead of `--release`; implies `NOT_CRAN` | -| `NOT_CRAN` | Disables CRAN flags (offline mode, job limits) | -| `R.version$platform` | If `wasm32-unknown-emscripten`, enables WebR-specific flags | - -When neither `DEBUG` nor `NOT_CRAN` is set and a `vendor.tar.xz` tarball is -present, `config.R` sets `@CRAN_FLAGS@` to `-j 2 --offline` to match CRAN's -build environment. The script then reads `src/Makevars.in` (or -`src/Makevars.win.in` on Windows), replaces all placeholders via `gsub()`, -and writes the result to `src/Makevars` (or `src/Makevars.win`). - -## Cleanup scripts - -### `cleanup` and `cleanup.win` - -These scripts are run by R when the package is uninstalled. They remove the -generated `Makevars` files, which are produced fresh at each build and should -not persist after uninstallation. - -```{.bash filename="inst/templates/cleanup"} -rm -f src/Makevars -``` - -```{.bash filename="inst/templates/cleanup.win"} -rm -f src/Makevars.win -``` - -## R templates - -### `extendr-wrappers.R` - -This template is written only once β€” with `overwrite = FALSE` hardcoded β€” -meaning it is only created if it does not already exist. Subsequent updates to -the file are handled automatically by the `document` binary at build time. The -initial template contains the `@useDynLib` directive needed to register the -package's compiled routines with R, plus the scaffolded `hello_world()` wrapper: - -```{.r filename="inst/templates/extendr-wrappers.R"} -# Generated by extendr: Do not edit by hand -# nolint start - -#' @usage NULL -#' @useDynLib {{{pkg_name}}}, .registration = TRUE -NULL - -#' Return string `"Hello world!"` to R. -#' @export -hello_world <- function() .Call(wrap__hello_world) - -# nolint end -``` - -After the first `devtools::document()` call, this file is fully regenerated by -`document.rs` and the initial template content is replaced. diff --git a/user-guide/package-structure.qmd b/user-guide/package-structure.qmd index 45d7ec2..002b146 100644 --- a/user-guide/package-structure.qmd +++ b/user-guide/package-structure.qmd @@ -47,8 +47,9 @@ only ever mess with `Cargo.toml` and scripts in `rust/src/`. ::: callout-note On this page, we only describe the content of files if it is helpful to you as an R package developer. If you want to know more about how these files are -generated and what they contain, you can have a look at the -[Scaffolding](../contributing/scaffolding.qmd) page in the Contributing guide. +generated, you can have a look at the +[{rextendr} Internals](../contributing/rextendr-internals.qmd) page in the +Contributing guide. ::: From 2990b480f0014697234ac5e838af75e9255ea486 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Tue, 17 Mar 2026 14:19:56 -0600 Subject: [PATCH 19/20] new contributing guide content --- contributing/documenting-code.qmd | 32 +++++++++++++++++++---- contributing/extendr-internals.qmd | 4 +++ contributing/testing-code.qmd | 39 ++++++++++++++++++++++++++- contributing/writing-code.qmd | 42 +++++++++++++++++++++++++----- 4 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 contributing/extendr-internals.qmd diff --git a/contributing/documenting-code.qmd b/contributing/documenting-code.qmd index 161b441..37ebdf5 100644 --- a/contributing/documenting-code.qmd +++ b/contributing/documenting-code.qmd @@ -19,13 +19,32 @@ accepted styling conventions in the R and Rust communities. ## Rust Documentation +All Rust code in extendr should be documented with +[rustdoc](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html) +following conventions recommended in the +[Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html). + +Doc examples that call the R API must be wrapped in `with_r`. Bare doctests +without it will fail at runtime. + +```rust +/// ``` +/// use extendr_api::prelude::*; +/// with_r(|| { +/// let x = r!([1, 2, 3]); +/// assert_eq!(x.len(), 3); +/// Ok(()) +/// }); +/// ``` +``` + ## R Documentation Functions in rextendr should be documented with [roxygen2](https://roxygen2.r-lib.org), following the conventions recommended by the [tidyverse style guide](https://style.tidyverse.org/documentation.html). -- Every exported function in rextendr must include the following roxygen - variables: +- Roxygen documentation for every exported function must include the following + roclets: - `@title` - `@description` β€” a concise description of what the function does. - `@param` β€” one tag per parameter, specifying: @@ -33,9 +52,12 @@ following the conventions recommended by the [tidyverse style guide](https://sty - whether it is a scalar or vector - the default value, if one exists - `@return` β€” what the function returns, including the R type. -- The `@details` and `@examples` sections are optional, but should in general be - included. + - `@examples` +- The `@details` section are optional, but should be included for most functions + in rextendr. - Internal functions should include roxygen comments if their use in rextendr is fairly complex, important, and not easy to discern. Simple helper functions do not need to be documented. - +- In vignettes and online guides, use the `extendrsrc` and `extendr` knitr + engines for examples involving Rust code. See `user-guide/serde-integration.qmd` + for a worked example to emulate. \ No newline at end of file diff --git a/contributing/extendr-internals.qmd b/contributing/extendr-internals.qmd new file mode 100644 index 0000000..e0cb11c --- /dev/null +++ b/contributing/extendr-internals.qmd @@ -0,0 +1,4 @@ +--- +title: "{extendr} Internals" +engine: knitr +--- \ No newline at end of file diff --git a/contributing/testing-code.qmd b/contributing/testing-code.qmd index 60f53f6..0d25db5 100644 --- a/contributing/testing-code.qmd +++ b/contributing/testing-code.qmd @@ -1,4 +1,41 @@ --- title: "Testing Code" engine: knitr ---- \ No newline at end of file +--- + +## Rust Tests + +Rust tests in extendr follow standard Rust conventions: `#[test]` functions +in `#[cfg(test)]` modules, either inline in the source file or in a separate +file under `tests/`. For details, see [Writing Automated Tests](https://doc.rust-lang.org/book/ch11-00-testing.html) +in The Book. + + + +### Starting an R session in tests + +An important wrinkle of testing Rust code in extendr is that most tests need a +live R session. In normal package use, R is already running when Rust code +executes. But when running `cargo test` directly, there is no R session β€” so any +test that touches an R object must start one first. + +Use the `test!` macro to wrap test code that calls the R API. It starts R and +allows `?` inside the block: + +```rust +#[test] +fn my_test() { + test! { + let x = r!([1, 2, 3]); + assert_eq!(x.len(), 3); + } +} +``` + +If a test does not call the R API at all β€” for example, testing pure Rust +arithmetic on scalar wrapper types β€” `test!` is not needed. + +## R Tests + +R code in rextendr is tested with [testthat](https://testthat.r-lib.org), +following standard R package testing conventions. diff --git a/contributing/writing-code.qmd b/contributing/writing-code.qmd index c591d6c..5c368ad 100644 --- a/contributing/writing-code.qmd +++ b/contributing/writing-code.qmd @@ -1,16 +1,46 @@ --- -title: "General writing conventions" +title: "Writing Code" engine: knitr --- This page covers code formatting conventions for the extendr project. Following these conventions keeps code consistent and readable across contributors. -## R code +## Rust Code -- Never use `print()` to display an R object. Let R print it implicitly. -- Use the `extendrsrc` and `extendr` knitr engines for all extendr code. See - `user-guide/serde-integration.qmd` for a worked example to emulate. +Rust code should be written to conform with conventions outlined in the +[Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html). +It's a good idea to read this document, but it may take time to gain some +familiarity with all of its ins and outs. In the meantime, the following tools +will prove very beneficial to keeping your Rust code consistent with those +conventions. -## Rust code +- Use [rustfmt](https://rust-lang.github.io/rustfmt/?version=v1.9.0) to + format Rust code. rustfmt is the standard Rust formatter and can be + configured to run on save in most editors. + +- Use [Clippy](https://doc.rust-lang.org/stable/clippy/) to lint Rust code. + Clippy catches common mistakes and enforces idiomatic Rust style. + +- Use [rust-analyzer](https://rust-analyzer.github.io/) for IDE support. + It provides code completion, inline errors, and other features in VS Code, + Positron, Zed, and other editors. + +## R Code + +R code should be written to conform with conventions outlined in the +[Tidyverse style guide](https://style.tidyverse.org/). What we said about the +Rust API guidelines, it applies here, too. In your effort to abide by those +conventions, you will find a great deal of advantage in these tools. + +- Use [Air](https://posit-dev.github.io/air/) to format R code. Air can be + configured to automatically format R scripts on save in your editor. Air + supports VS Code, Positron, Zed, and the command line. + +- Use [lintr](https://lintr.r-lib.org/) to lint R code. rextendr's CI uses + lintr, so having it configured in your editor will catch issues before they + are flagged in review. lintr supports most IDEs, include RStudio, VS Code, and + Positron. You may also like [jarl](https://jarl.etiennebacher.com/), an R + lintr written in Rust, which is much faster than lintr and supports additional + IDEs. From a1ec91f60471f38ec65f840e6b08532ada223b51 Mon Sep 17 00:00:00 2001 From: Blake Vernon Date: Tue, 17 Mar 2026 15:58:59 -0600 Subject: [PATCH 20/20] fix navbar background --- css/extendr.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/extendr.scss b/css/extendr.scss index 31a4918..cebb0e4 100644 --- a/css/extendr.scss +++ b/css/extendr.scss @@ -76,7 +76,7 @@ $link-color: color.mix(black, $teal-4, 20%, $method: oklch); $link-hover-color: color.mix(black, $teal-4, 40%, $method: oklch); $nav-link-color: $link-color; $nav-link-hover-color: $link-hover-color; -$navbar-bg: transparent; +$navbar-bg: white; $navbar-fg: black; // fonts @@ -297,7 +297,7 @@ body.quarto-dark { } .navbar { - background-color: transparent !important; + background-color: var(--bs-body-bg); border: none; color: white;