From f2f817fe2b64254255c8bff3c59895eff86baef4 Mon Sep 17 00:00:00 2001 From: Arnav Nagzirkar <113314200+arnavnagzirkar@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:46:54 -0700 Subject: [PATCH 1/3] docs: add rust_icu hello world example --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 710f007..99fcc32 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,55 @@ Crate [rust_icu_utext](https://crates.io/crates/rust_icu_utext) | Text operations. Implements [`utext.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/utext_8h.html) C API header from the ICU library. [rust_icu_utrans](https://crates.io/crates/rust_icu_utrans) | Transliteration support. Implements [`utrans.h`](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/utrans_8h.html) C API header from the ICU library. +# Hello, World! + +This section shows a minimal example of using `rust_icu` for locale-aware +[MessageFormat](http://userguide.icu-project.org/formatparse/messages) string +formatting. MessageFormat is *not* XLIFF; it is a pattern-based template +language built into ICU that lets you embed formatted numbers, dates, and +plurals directly inside a message string. + +## 1. Add the dependencies + +In your `Cargo.toml`: + +```toml +[dependencies] +rust_icu_common = "5.6" +rust_icu_uloc = "5.6" +rust_icu_umsg = "5.6" +rust_icu_ustring = "5.6" +``` + +## 2. Write the code + +```rust,ignore +use rust_icu_common as common; +use rust_icu_uloc as uloc; +use rust_icu_umsg::{self as umsg, message_format}; +use rust_icu_ustring as ustring; +use std::convert::TryFrom; + +fn main() -> Result<(), common::Error> { + // Choose a locale. + let loc = uloc::ULoc::try_from("en-US")?; + + // Write a MessageFormat pattern. {0} is the first positional argument. + let pattern = ustring::UChar::try_from("Hello, {0}!")?; + let fmt = umsg::UMessageFormat::try_from(&pattern, &loc)?; + + // Format the message by binding a value to each positional parameter. + let name = ustring::UChar::try_from("World")?; + let result = message_format!(fmt, { name => String })?; + + println!("{}", result); // Hello, World! + Ok(()) +} +``` + +See the [`rust_icu_umsg` crate docs](https://docs.rs/rust_icu_umsg) for more +advanced patterns (numbers, dates, plural rules, etc.). + # Limitations The generated rust language binding methods of today limit the availability of From 30e30fad0ffdf0ce8337f45ad5c79ebb68a80644 Mon Sep 17 00:00:00 2001 From: Arnav Nagzirkar <113314200+arnavnagzirkar@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:08:57 -0700 Subject: [PATCH 2/3] docs: add runnable examples/hello_world crate Address review: move the Hello, World! demo into a standalone, runnable examples/hello_world crate (Cargo.toml + src/main.rs) instead of an inline README snippet. The crate is excluded from the workspace so the CI feature matrix is unaffected, and the top-level README now links to it. --- Cargo.toml | 7 ++++ README.md | 56 ++++++++------------------------ examples/hello_world/Cargo.toml | 29 +++++++++++++++++ examples/hello_world/README.md | 34 +++++++++++++++++++ examples/hello_world/src/main.rs | 46 ++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 examples/hello_world/Cargo.toml create mode 100644 examples/hello_world/README.md create mode 100644 examples/hello_world/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 421e196..5bf7ee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,11 @@ members = [ "rust_icu_utext", "rust_icu_utrans", ] +# Examples are standalone crates that build against the workspace crates via +# `path` dependencies. They are excluded from the workspace so the feature +# matrix used in CI (`cargo test --no-default-features --features ...`) does +# not have to know about them. +exclude = [ + "examples/hello_world", +] resolver = "2" diff --git a/README.md b/README.md index 99fcc32..2a2161f 100644 --- a/README.md +++ b/README.md @@ -84,52 +84,24 @@ Crate # Hello, World! -This section shows a minimal example of using `rust_icu` for locale-aware +A complete, runnable "Hello, World!" lives in +[`examples/hello_world`](examples/hello_world). It shows locale-aware [MessageFormat](http://userguide.icu-project.org/formatparse/messages) string -formatting. MessageFormat is *not* XLIFF; it is a pattern-based template -language built into ICU that lets you embed formatted numbers, dates, and -plurals directly inside a message string. +formatting (MessageFormat is *not* XLIFF; it is a pattern-based template +language built into ICU). Run it with: -## 1. Add the dependencies - -In your `Cargo.toml`: - -```toml -[dependencies] -rust_icu_common = "5.6" -rust_icu_uloc = "5.6" -rust_icu_umsg = "5.6" -rust_icu_ustring = "5.6" -``` - -## 2. Write the code - -```rust,ignore -use rust_icu_common as common; -use rust_icu_uloc as uloc; -use rust_icu_umsg::{self as umsg, message_format}; -use rust_icu_ustring as ustring; -use std::convert::TryFrom; - -fn main() -> Result<(), common::Error> { - // Choose a locale. - let loc = uloc::ULoc::try_from("en-US")?; - - // Write a MessageFormat pattern. {0} is the first positional argument. - let pattern = ustring::UChar::try_from("Hello, {0}!")?; - let fmt = umsg::UMessageFormat::try_from(&pattern, &loc)?; - - // Format the message by binding a value to each positional parameter. - let name = ustring::UChar::try_from("World")?; - let result = message_format!(fmt, { name => String })?; - - println!("{}", result); // Hello, World! - Ok(()) -} +```sh +cd examples/hello_world +cargo run +# => Hello, World! ``` -See the [`rust_icu_umsg` crate docs](https://docs.rs/rust_icu_umsg) for more -advanced patterns (numbers, dates, plural rules, etc.). +The example's [`Cargo.toml`](examples/hello_world/Cargo.toml) lists the +dependencies you need (`rust_icu_common`, `rust_icu_uloc`, `rust_icu_umsg`, +`rust_icu_ustring`), and [`src/main.rs`](examples/hello_world/src/main.rs) +contains the code. See the +[`rust_icu_umsg` crate docs](https://docs.rs/rust_icu_umsg) for more advanced +patterns (numbers, dates, plural rules, etc.). # Limitations diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml new file mode 100644 index 0000000..cb0d521 --- /dev/null +++ b/examples/hello_world/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hello_world" +version = "0.0.0" +edition = "2018" +license = "Apache-2.0" +publish = false +description = "Minimal rust_icu MessageFormat 'Hello, World!' example." + +# This example is intentionally kept out of the top-level workspace (see the +# `exclude` entry in ../../Cargo.toml) so it can be built on its own: +# +# cd examples/hello_world +# cargo run +# +# The dependencies below use `path` so the example always builds against the +# crates in this repository. A downstream user would instead depend on the +# published crates, e.g.: +# +# [dependencies] +# rust_icu_common = "5.6" +# rust_icu_uloc = "5.6" +# rust_icu_umsg = "5.6" +# rust_icu_ustring = "5.6" + +[dependencies] +rust_icu_common = { path = "../../rust_icu_common" } +rust_icu_uloc = { path = "../../rust_icu_uloc" } +rust_icu_umsg = { path = "../../rust_icu_umsg" } +rust_icu_ustring = { path = "../../rust_icu_ustring" } diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md new file mode 100644 index 0000000..ff1df6e --- /dev/null +++ b/examples/hello_world/README.md @@ -0,0 +1,34 @@ +# `rust_icu` Hello, World! + +A minimal, runnable example of locale-aware +[MessageFormat](http://userguide.icu-project.org/formatparse/messages) string +formatting with `rust_icu`. `MessageFormat` is a pattern-based template +language built into ICU that lets you embed formatted numbers, dates, and +plurals directly inside a message string. It is *not* XLIFF. + +## Run it + +You need a working ICU installation, the same as for the rest of `rust_icu` +(see the [top-level README](../../README.md)). Then: + +```sh +cd examples/hello_world +cargo run +``` + +Expected output: + +```text +Hello, World! +``` + +## What it shows + +- [`Cargo.toml`](Cargo.toml) — the dependencies required to format a message: + `rust_icu_common`, `rust_icu_uloc`, `rust_icu_umsg`, and `rust_icu_ustring`. +- [`src/main.rs`](src/main.rs) — building a `UMessageFormat` from a pattern and + a locale, then formatting a positional argument with the + [`message_format!`](https://docs.rs/rust_icu_umsg) macro. + +See the [`rust_icu_umsg` crate docs](https://docs.rs/rust_icu_umsg) for more +advanced patterns (numbers, dates, plural rules, etc.). diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs new file mode 100644 index 0000000..e108f7c --- /dev/null +++ b/examples/hello_world/src/main.rs @@ -0,0 +1,46 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A minimal "Hello, World!" example for `rust_icu`. +//! +//! It uses [`rust_icu_umsg`] (ICU `MessageFormat`) to format a locale-aware +//! message. `MessageFormat` is a pattern-based template language built into +//! ICU; it is *not* XLIFF. Run it with: +//! +//! ```text +//! cargo run +//! ``` + +use rust_icu_common as common; +use rust_icu_uloc as uloc; +use rust_icu_umsg::{self as umsg, message_format}; +use rust_icu_ustring as ustring; +use std::convert::TryFrom; + +fn main() -> Result<(), common::Error> { + // Choose a locale. Formatting of numbers, dates and plurals is driven by + // the locale's ICU data. + let loc = uloc::ULoc::try_from("en-US")?; + + // A MessageFormat pattern. `{0}` is the first positional argument. + let pattern = ustring::UChar::try_from("Hello, {0}!")?; + let fmt = umsg::UMessageFormat::try_from(&pattern, &loc)?; + + // Bind a value to each positional parameter and format the message. + let name = ustring::UChar::try_from("World")?; + let result = message_format!(fmt, { name => String })?; + + println!("{}", result); // Hello, World! + Ok(()) +} From 3b3d1096b58b9b6ef64d4d0f0cc157d4e71a0334 Mon Sep 17 00:00:00 2001 From: Arnav Nagzirkar <113314200+arnavnagzirkar@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:27:14 -0700 Subject: [PATCH 3/3] ci: build and run hello_world example in presubmits Adds a test-examples CI job and a make docker-test-example target that build and run the examples/hello_world crate inside the existing dockerized test environment across ICU 74, 76 and 77. The example now exposes a greet() function with a unit test asserting the formatted output equals Hello, World!, so the presubmits verify the example compiles, runs and produces the expected output. --- .github/workflows/test.yml | 9 +++++++++ Makefile | 30 ++++++++++++++++++++++++++++++ examples/hello_world/README.md | 16 ++++++++++++++++ examples/hello_world/src/main.rs | 23 ++++++++++++++++++++--- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0dcf319..3b5c208 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,15 @@ jobs: - uses: actions/checkout@v6 - name: 'Test ICU version ${{ matrix.icu_version }}' run: 'make DOCKER_TEST_ENV=rust_icu_testenv-${{ matrix.icu_version}} docker-test' + test-examples: + runs-on: ubuntu-latest + strategy: + matrix: + icu_version: [74, 76, 77] + steps: + - uses: actions/checkout@v6 + - name: 'Build and run examples on ICU version ${{ matrix.icu_version }}' + run: 'make DOCKER_TEST_ENV=rust_icu_testenv-${{ matrix.icu_version }} RUST_ICU_MAJOR_VERSION_NUMBER=${{ matrix.icu_version }} docker-test-example' test-with-features: runs-on: ubuntu-latest strategy: diff --git a/Makefile b/Makefile index a9df912..4548530 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,36 @@ docker-test-current: ${DOCKER_REPO}/rust_icu_testenv-current:${USED_BUILDENV_VERSION} .PHONY: docker-test-current +# Builds and runs the standalone examples (examples/*) inside the dockerized +# test environment. The example crates are excluded from the workspace, so the +# regular docker-test target does not touch them; this target gives the +# presubmits a way to confirm the documented example actually compiles, runs and +# prints the expected output. It overrides the image entrypoint and drives +# cargo directly because the examples are built with default features, unlike +# the feature-matrix runs. +docker-test-example: + mkdir -p ${CARGO_TARGET_DIR} + echo top_dir: ${TOP_DIR} + echo pwd: $(shell pwd) + docker run ${TTY} \ + --user=${UID}:${GID} \ + --volume=${TOP_DIR}:/src/rust_icu \ + --volume=${CARGO_TARGET_DIR}:/build/cargo \ + --volume=${LOGNAME_HOME}/.cargo:/usr/local/cargo \ + --env="RUST_ICU_MAJOR_VERSION_NUMBER=${RUST_ICU_MAJOR_VERSION_NUMBER}" \ + --env="RUST_BACKTRACE=full" \ + --entrypoint="/bin/bash" \ + ${DOCKER_REPO}/${DOCKER_TEST_ENV}:${USED_BUILDENV_VERSION} \ + "-l" "-c" "set -eo pipefail; \ + for example in /src/rust_icu/examples/*/; do \ + ( cd \"$$example\" \ + && env LD_LIBRARY_PATH=/usr/local/lib \ + cargo test --target-dir=/build/cargo \ + && env LD_LIBRARY_PATH=/usr/local/lib \ + cargo run --target-dir=/build/cargo ); \ + done" +.PHONY: docker-test-example + # Test with the homebrew version of icu4c on macOS, running bindgen natively # using the ICU headers provided by Homebrew and the clang toolchain from Xcode. # Two passes: dynamic linking (default) and static linking. diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md index ff1df6e..4b25c52 100644 --- a/examples/hello_world/README.md +++ b/examples/hello_world/README.md @@ -22,6 +22,22 @@ Expected output: Hello, World! ``` +## Verified by presubmits + +This example is not just documentation. The project's CI builds and runs it on +every push and pull request, across the same ICU versions as the rest of the +test matrix. The `make docker-test-example` target builds the crate inside the +dockerized test environment, runs `cargo test` (which asserts that the formatted +output equals `Hello, World!`), and then runs the binary with `cargo run`. If +the example ever stops compiling or stops producing the expected output, CI +fails. + +You can reproduce the presubmit locally with: + +```sh +make DOCKER_TEST_ENV=rust_icu_testenv-77 RUST_ICU_MAJOR_VERSION_NUMBER=77 docker-test-example +``` + ## What it shows - [`Cargo.toml`](Cargo.toml) — the dependencies required to format a message: diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index e108f7c..ae143bc 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -21,6 +21,10 @@ //! ```text //! cargo run //! ``` +//! +//! The formatting logic lives in [`greet`] so it can be exercised by a unit +//! test (see the bottom of this file). That test is what lets the project's +//! presubmits confirm the example actually produces the expected output. use rust_icu_common as common; use rust_icu_uloc as uloc; @@ -28,7 +32,8 @@ use rust_icu_umsg::{self as umsg, message_format}; use rust_icu_ustring as ustring; use std::convert::TryFrom; -fn main() -> Result<(), common::Error> { +/// Formats a locale-aware "Hello, World!" using ICU `MessageFormat`. +fn greet() -> Result { // Choose a locale. Formatting of numbers, dates and plurals is driven by // the locale's ICU data. let loc = uloc::ULoc::try_from("en-US")?; @@ -39,8 +44,20 @@ fn main() -> Result<(), common::Error> { // Bind a value to each positional parameter and format the message. let name = ustring::UChar::try_from("World")?; - let result = message_format!(fmt, { name => String })?; + message_format!(fmt, { name => String }) +} - println!("{}", result); // Hello, World! +fn main() -> Result<(), common::Error> { + println!("{}", greet()?); // Hello, World! Ok(()) } + +#[cfg(test)] +mod tests { + use super::greet; + + #[test] + fn formats_hello_world() { + assert_eq!(greet().expect("formatting should succeed"), "Hello, World!"); + } +}