From: DongHun Kwak Date: Tue, 22 Apr 2025 05:00:48 +0000 (+0900) Subject: Import cxx 1.0.157 X-Git-Tag: upstream/1.0.157 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=66e8989394f2cf75566465c9e6a0f54c78cdb3f5;p=platform%2Fupstream%2Frust-cxx.git Import cxx 1.0.157 --- 66e8989394f2cf75566465c9e6a0f54c78cdb3f5 diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..c42dab3 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,2 @@ +target/ +tools/buck/buck2/ diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..09d078e --- /dev/null +++ b/.bazelrc @@ -0,0 +1,21 @@ +############################################################################### +## Bazel Configuration Flags +## +## `.bazelrc` is a Bazel configuration file. +## https://bazel.build/docs/best-practices#bazelrc-file +############################################################################### + +build --enable_platform_specific_config +build:linux --@rules_rust//:extra_rustc_flags=-Clink-arg=-fuse-ld=lld +build:linux --cxxopt=-std=c++17 +build:macos --cxxopt=-std=c++17 + +############################################################################### +## Custom user flags +## +## This should always be the last thing in the `.bazelrc` file to ensure +## consistent behavior when setting flags in that file as `.bazelrc` files are +## evaluated top to bottom. +############################################################################### + +try-import %workspace%/user.bazelrc diff --git a/.bcr/README.md b/.bcr/README.md new file mode 100644 index 0000000..44ae7fe --- /dev/null +++ b/.bcr/README.md @@ -0,0 +1,9 @@ +# Bazel Central Registry + +When the ruleset is released, we want it to be published to the +Bazel Central Registry automatically: + + +This folder contains configuration files to automate the publish step. +See +for authoritative documentation about these files. diff --git a/.bcr/config.yml b/.bcr/config.yml new file mode 100644 index 0000000..8531afc --- /dev/null +++ b/.bcr/config.yml @@ -0,0 +1,3 @@ +fixedReleaser: + login: dtolnay + email: dtolnay@gmail.com diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json new file mode 100644 index 0000000..0982309 --- /dev/null +++ b/.bcr/metadata.template.json @@ -0,0 +1,16 @@ +{ + "homepage": "https://cxx.rs", + "maintainers": [ + { + "github": "dtolnay", + "github_user_id": 1940490, + "email": "dtolnay@gmail.com", + "name": "David Tolnay" + } + ], + "repository": [ + "github:dtolnay/cxx" + ], + "versions": [], + "yanked_versions": {} +} diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml new file mode 100644 index 0000000..b5083f5 --- /dev/null +++ b/.bcr/presubmit.yml @@ -0,0 +1,15 @@ +matrix: + platform: + - macos_arm64 + - ubuntu2404 + - windows + bazel: [7.x, 8.x] +tasks: + verify_targets: + name: Verify build targets + platform: ${{ platform }} + bazel: ${{ bazel }} + build_targets: + - '@cxx.rs//...' + test_targets: + - '@cxx.rs//...' diff --git a/.bcr/source.template.json b/.bcr/source.template.json new file mode 100644 index 0000000..902c238 --- /dev/null +++ b/.bcr/source.template.json @@ -0,0 +1,5 @@ +{ + "integrity": "", + "strip_prefix": "{REPO}-{VERSION}", + "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{VERSION}.tar.gz" +} diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000..1878b58 --- /dev/null +++ b/.buckconfig @@ -0,0 +1,30 @@ +[cells] +root = . +prelude = tools/buck/prelude +toolchains = tools/buck/toolchains +none = none + +[external_cells] +prelude = bundled + +[cell_aliases] +config = prelude +fbcode = none +fbsource = none + +[project] +# Hide BUCK files under target/package/ from `buck build ...`. Otherwise: +# $ buck build ... +# //target/package/cxx-0.3.0/tests:ffi references non-existing file or directory 'target/package/cxx-0.3.0/tests/ffi/lib.rs' +# +# Also hide some Bazel-managed directories that contain symlinks to the repo root. +ignore = \ + .git, \ + bazel-bin, \ + bazel-cxx, \ + bazel-out, \ + bazel-testlogs, \ + target + +[parser] +target_platform_detector_spec = target:root//...->prelude//platforms:default diff --git a/.buckroot b/.buckroot new file mode 100644 index 0000000..e69de29 diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..76a464f --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "f9d547b60324bc02d9983622159973a75d06ea10" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8ea286f --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +AlwaysBreakTemplateDeclarations: true +MaxEmptyLinesToKeep: 3 +ReflowComments: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..671d539 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,20 @@ +Checks: + clang-analyzer-*, + clang-diagnostic-*, + cppcoreguidelines-*, + modernize-*, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-const-cast, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + -modernize-return-braced-init-list, + -modernize-use-default-member-init, + -modernize-use-equals-default, + -modernize-use-trailing-return-type, +HeaderFilterRegex: cxx\.h diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..93cfc05 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1 @@ +FROM dtolnay/devcontainer:latest diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000..b30aebc --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,4 @@ +This directory contains the container setup used when developing CXX inside of +GitHub [Codespaces]. + +[Codespaces]: https://github.com/features/codespaces diff --git a/.devcontainer/build.Dockerfile b/.devcontainer/build.Dockerfile new file mode 100644 index 0000000..74ddd7b --- /dev/null +++ b/.devcontainer/build.Dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/devcontainers/rust:bookworm + +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends clang lld zstd \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && wget -q -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64 \ + && wget -q -O /tmp/buck.zst https://github.com/facebook/buck2/releases/download/latest/buck2-x86_64-unknown-linux-gnu.zst \ + && wget -q -O /usr/local/bin/buildifier https://github.com/bazelbuild/buildtools/releases/latest/download/buildifier-linux-amd64 \ + && unzstd /tmp/buck.zst -o /usr/local/bin/buck \ + && chmod +x /usr/local/bin/bazel /usr/local/bin/buck /usr/local/bin/buildifier \ + && rm /tmp/buck.zst \ + && rustup component add rust-analyzer rust-src diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b5b2911 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Rust", + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "lldb.executable": "/usr/bin/lldb", + "files.watcherExclude": { + "**/target/**": true + } + }, + "extensions": [ + "BazelBuild.vscode-bazel", + "ms-vscode.cpptools", + "rust-lang.rust-analyzer", + "vadimcn.vscode-lldb" + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1cdc71c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +MODULE.bazel.lock linguist-generated +third-party/BUCK linguist-generated +third-party/bazel/** linguist-generated diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7507077 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: dtolnay diff --git a/.github/workflows/buck2.yml b/.github/workflows/buck2.yml new file mode 100644 index 0000000..9c3c54a --- /dev/null +++ b/.github/workflows/buck2.yml @@ -0,0 +1,28 @@ +name: Buck2 + +on: + push: + workflow_dispatch: + schedule: [cron: "40 1,13 * * *"] + +permissions: + contents: read + +jobs: + buck2: + name: Buck2 on ${{matrix.os == 'ubuntu' && 'Linux' || matrix.os == 'macos' && 'macOS' || matrix.os == 'windows' && 'Windows' || '???'}} + runs-on: ${{matrix.os}}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rust-src + - uses: dtolnay/install-buck2@latest + - run: buck2 run demo + - run: buck2 build ... + - run: buck2 test ... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b52099e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,224 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + schedule: [cron: "40 1 * * *"] + +permissions: + contents: read + +jobs: + pre_ci: + uses: dtolnay/.github/.github/workflows/pre_ci.yml@master + + test: + name: ${{matrix.name || format('Rust {0}', matrix.rust)}} + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ${{matrix.os}}-latest + strategy: + fail-fast: false + matrix: + rust: [nightly, beta, stable, 1.82.0, 1.80.0, 1.77.0, 1.74.0, 1.73.0] + os: [ubuntu] + cc: [''] + flags: [''] + include: + - name: Cargo on macOS + rust: nightly + os: macos + - name: Cargo on Windows (msvc) + rust: nightly-x86_64-pc-windows-msvc + os: windows + flags: /EHsc + - name: Clang + rust: nightly + cc: clang++ + os: ubuntu + flags: -std=c++20 -Werror -Wall + - name: C++14 + rust: nightly + os: ubuntu + flags: -std=c++14 -Werror -Wall + - name: C++17 + rust: nightly + os: ubuntu + flags: -std=c++17 -Werror -Wall + - name: C++20 + rust: nightly + os: ubuntu + flags: -std=c++20 -Werror -Wall + env: + CXX: ${{matrix.cc}} + CXXFLAGS: ${{matrix.flags}} + RUSTFLAGS: --cfg deny_warnings -Dwarnings + timeout-minutes: 45 + steps: + - name: Enable symlinks (windows) + if: matrix.os == 'windows' + run: git config --global core.symlinks true + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{matrix.rust}} + components: rust-src + - name: Determine test suite subset + # Our Windows and macOS jobs are the longest running, so exclude the + # relatively slow compiletest from them to speed up end-to-end CI time, + # except during cron builds when no human is presumably waiting on the + # build. The extra coverage is not particularly valuable and we can + # still ensure the test is kept passing on the basis of the scheduled + # builds. + run: | + echo RUSTFLAGS=$RUSTFLAGS >> $GITHUB_ENV + echo exclude=--exclude cxx-test-suite ${{matrix.rust == '1.73.0' && '--exclude cxxbridge-cmd' || ''}} >> $GITHUB_OUTPUT + env: + RUSTFLAGS: ${{env.RUSTFLAGS}} ${{matrix.os != 'ubuntu' && github.event_name != 'schedule' && '--cfg skip_ui_tests' || ''}} + id: testsuite + shell: bash + - name: Ignore macOS linker warning + run: echo RUSTFLAGS=${RUSTFLAGS}\ -Alinker_messages >> $GITHUB_ENV + if: matrix.os == 'macos' + - run: cargo run --manifest-path demo/Cargo.toml + - run: cargo test --workspace ${{steps.testsuite.outputs.exclude}} + if: matrix.rust != '1.74.0' && matrix.rust != '1.73.0' + - run: cargo check --no-default-features --features alloc + env: + RUSTFLAGS: --cfg compile_error_if_std ${{env.RUSTFLAGS}} + - run: cargo check --no-default-features + env: + RUSTFLAGS: --cfg compile_error_if_alloc --cfg cxx_experimental_no_alloc ${{env.RUSTFLAGS}} + - uses: actions/upload-artifact@v4 + if: matrix.os == 'ubuntu' && matrix.rust == 'nightly' && always() + with: + name: Cargo.lock + path: Cargo.lock + continue-on-error: true + + reindeer: + name: Reindeer + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rust-src + - uses: dtolnay/install@reindeer + - run: reindeer buckify + working-directory: third-party + - name: Check reindeer-generated BUCK file up to date + run: git diff --exit-code + + bazel: + name: Bazel on ${{matrix.os == 'ubuntu' && 'Linux' || matrix.os == 'macos' && 'macOS' || matrix.os == 'windows' && 'Windows' || '???'}} + runs-on: ${{matrix.os}}-latest + if: github.event_name != 'pull_request' + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - name: Install lld + run: sudo apt-get install lld + if: matrix.os == 'ubuntu' + - name: Set bazelrc for Windows + run: echo "startup --output_user_root=D:/bzl" > user.bazelrc + if: matrix.os == 'windows' + - run: bazel --version + - run: bazel run demo --verbose_failures --noshow_progress ${{matrix.os == 'macos' && '--xcode_version_config=tools/bazel:github_actions_xcodes' || ''}} + - run: bazel test ... --verbose_failures --noshow_progress ${{matrix.os == 'macos' && '--xcode_version_config=tools/bazel:github_actions_xcodes' || ''}} + - name: Check MODULE.bazel.lock up to date + run: git diff --exit-code + - run: bazel run //third-party:vendor + if: matrix.os == 'ubuntu' || matrix.os == 'macos' + - name: Check third-party/bazel up to date + run: git diff --exit-code + if: matrix.os == 'ubuntu' || matrix.os == 'macos' + + minimal: + name: Minimal versions + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo generate-lockfile -Z minimal-versions + - run: cargo check --locked --workspace + + doc: + name: Documentation + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - uses: dtolnay/install@cargo-docs-rs + - run: cargo docs-rs + - run: cargo docs-rs -p cxx-build + - run: cargo docs-rs -p cxx-gen + - run: cargo docs-rs -p cxxbridge-flags + - run: cargo docs-rs -p cxxbridge-macro + + clippy: + name: Clippy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + env: + RUSTFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: clippy, rust-src + - run: cargo clippy --workspace --tests --exclude demo -- -Dclippy::all -Dclippy::pedantic + - run: cargo clippy --manifest-path demo/Cargo.toml -- -Dclippy::all + + clang-tidy: + name: Clang Tidy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - name: Install clang-tidy + run: sudo apt-get install clang-tidy-18 + - name: Run clang-tidy + run: clang-tidy-18 src/cxx.cc --warnings-as-errors=* + + eslint: + name: ESLint + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - run: npm install + working-directory: book + - run: npx eslint + working-directory: book + + outdated: + name: Outdated + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/install@cargo-outdated + - run: cargo outdated --workspace --exit-code 1 diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 0000000..025fe23 --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,18 @@ +name: Install + +on: + workflow_dispatch: + schedule: [cron: "40 1 * * *"] + push: {tags: ['*']} + +permissions: {} + +env: + RUSTFLAGS: -Dwarnings + +jobs: + install: + name: Install + uses: dtolnay/.github/.github/workflows/check_install.yml@master + with: + crate: cxxbridge-cmd diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..196e389 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + release: + types: [released] + +permissions: + attestations: write + contents: write + id-token: write + +jobs: + upload: + uses: dtolnay/.github/.github/workflows/release_tgz.yml@master + + publish-to-bcr: + needs: upload + uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@47913235f61615d02c989d652c4d10c45c0c4f0b + with: + tag_name: ${{github.event.release.tag_name}} + registry_fork: dtolnay-contrib/bazel-central-registry + secrets: + publish_token: ${{secrets.PUBLISH_TOKEN}} diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml new file mode 100644 index 0000000..555be19 --- /dev/null +++ b/.github/workflows/site.yml @@ -0,0 +1,37 @@ +name: Deploy + +on: + push: + branches: + - master + paths: + - book/** + - .github/workflows/site.yml + workflow_dispatch: + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + permissions: + contents: write + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/install@mdbook + - run: mdbook --version + + - name: Build + run: book/build.sh + + - name: Push to gh-pages + working-directory: book/build + run: | + REV=$(git rev-parse --short HEAD) + git init + git remote add upstream https://x-access-token:${{secrets.GITHUB_TOKEN}}@github.com/dtolnay/cxx + git config user.name "CXX" + git config user.email "dtolnay+cxx@gmail.com" + git add -A . + git commit -qm "Website @ ${{github.repository}}@${REV}" + git push -q upstream HEAD:refs/heads/gh-pages --force diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b6f5c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/.buckd +/bazel-bin +/bazel-cxx +/bazel-out +/bazel-testlogs +/user.bazelrc +/buck-out +/expand.cc +/expand.rs +/target/ +/Cargo.lock diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 0000000..5ed5b27 --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,4 @@ +VS Code actions and configuration. Applicable when developing CXX inside of +GitHub [Codespaces]. + +[Codespaces]: https://github.com/features/codespaces diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0218f47 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run cxx demo", + "type": "lldb", + "request": "launch", + "cargo": { + "args": ["build", "--manifest-path", "demo/Cargo.toml"], + "filter": { + "name": "demo", + "kind": "bin" + } + } + }, + { + "name": "Debug cargo tests", + "type": "lldb", + "request": "launch", + "cargo": { + "args": ["test", "--no-run"], + "filter": { + "name": "test", + "kind": "test" + } + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8a1c2c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "search.exclude": { + "**/target": true + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..44a2ab7 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Cargo test", + "type": "shell", + "command": "cargo test", + "group": "test" + }, + { + "label": "Bazel test", + "type": "shell", + "command": "bazel test ...", + "group": "test", + "dependsOn": ["Vendor"] + }, + { + "label": "Buck test", + "type": "shell", + "command": "buck test ...", + "group": "test", + "dependsOn": ["Vendor"] + }, + { + "label": "Vendor", + "type": "shell", + "command": "cp third-party/Cargo.lock . && cargo vendor --versioned-dirs --locked third-party/vendor" + } + ] +} diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..d93f308 --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["buck-out"] +} diff --git a/BUCK b/BUCK new file mode 100644 index 0000000..dccc386 --- /dev/null +++ b/BUCK @@ -0,0 +1,100 @@ +rust_library( + name = "cxx", + srcs = glob(["src/**/*.rs"]), + doc_deps = [ + ":cxx-build", + ], + edition = "2021", + features = [ + "alloc", + "std", + ], + visibility = ["PUBLIC"], + deps = [ + ":core", + ":cxxbridge-macro", + "//third-party:foldhash", + ], +) + +alias( + name = "codegen", + actual = ":cxxbridge", + visibility = ["PUBLIC"], +) + +rust_binary( + name = "cxxbridge", + srcs = glob(["gen/cmd/src/**/*.rs"]) + [ + "gen/cmd/src/gen", + "gen/cmd/src/syntax", + ], + edition = "2021", + deps = [ + "//third-party:clap", + "//third-party:codespan-reporting", + "//third-party:proc-macro2", + "//third-party:quote", + "//third-party:syn", + ], +) + +cxx_library( + name = "core", + srcs = ["src/cxx.cc"], + exported_headers = { + "cxx.h": "include/cxx.h", + }, + header_namespace = "rust", + preferred_linkage = "static", + visibility = ["PUBLIC"], +) + +rust_library( + name = "cxxbridge-macro", + srcs = glob(["macro/src/**/*.rs"]) + ["macro/src/syntax"], + doctests = False, + edition = "2021", + proc_macro = True, + deps = [ + "//third-party:proc-macro2", + "//third-party:quote", + "//third-party:rustversion", + "//third-party:syn", + ], +) + +rust_library( + name = "cxx-build", + srcs = glob(["gen/build/src/**/*.rs"]) + [ + "gen/build/src/gen", + "gen/build/src/syntax", + ], + doctests = False, + edition = "2021", + deps = [ + "//third-party:cc", + "//third-party:codespan-reporting", + "//third-party:proc-macro2", + "//third-party:quote", + "//third-party:scratch", + "//third-party:syn", + ], +) + +rust_library( + name = "cxx-gen", + srcs = glob(["gen/lib/src/**/*.rs"]) + [ + "gen/lib/src/gen", + "gen/lib/src/syntax", + ], + edition = "2021", + visibility = ["PUBLIC"], + deps = [ + "//third-party:cc", + "//third-party:codespan-reporting", + "//third-party:proc-macro2", + "//third-party:quote", + "//third-party:syn", + ], +) diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..f1e2f92 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,99 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro") + +rust_library( + name = "cxx", + srcs = glob(["src/**/*.rs"]), + crate_features = [ + "alloc", + "std", + ], + edition = "2021", + proc_macro_deps = [ + ":cxxbridge-macro", + ], + visibility = ["//visibility:public"], + deps = [ + ":core-lib", + "@crates.io//:foldhash", + ], +) + +alias( + name = "codegen", + actual = ":cxxbridge", + visibility = ["//visibility:public"], +) + +rust_binary( + name = "cxxbridge", + srcs = glob(["gen/cmd/src/**/*.rs"]), + compile_data = ["gen/cmd/src/gen/include/cxx.h"], + edition = "2021", + deps = [ + "@crates.io//:clap", + "@crates.io//:codespan-reporting", + "@crates.io//:proc-macro2", + "@crates.io//:quote", + "@crates.io//:syn", + ], +) + +cc_library( + name = "core", + hdrs = ["include/cxx.h"], + include_prefix = "rust", + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +cc_library( + name = "core-lib", + srcs = ["src/cxx.cc"], + hdrs = ["include/cxx.h"], + linkstatic = True, +) + +rust_proc_macro( + name = "cxxbridge-macro", + srcs = glob(["macro/src/**/*.rs"]), + edition = "2021", + proc_macro_deps = [ + "@crates.io//:rustversion", + ], + deps = [ + "@crates.io//:proc-macro2", + "@crates.io//:quote", + "@crates.io//:syn", + ], +) + +rust_library( + name = "cxx-build", + srcs = glob(["gen/build/src/**/*.rs"]), + compile_data = ["gen/build/src/gen/include/cxx.h"], + edition = "2021", + deps = [ + "@crates.io//:cc", + "@crates.io//:codespan-reporting", + "@crates.io//:proc-macro2", + "@crates.io//:quote", + "@crates.io//:scratch", + "@crates.io//:syn", + ], +) + +rust_library( + name = "cxx-gen", + srcs = glob(["gen/lib/src/**/*.rs"]), + compile_data = ["gen/lib/src/gen/include/cxx.h"], + edition = "2021", + visibility = ["//visibility:public"], + deps = [ + "@crates.io//:cc", + "@crates.io//:codespan-reporting", + "@crates.io//:proc-macro2", + "@crates.io//:quote", + "@crates.io//:syn", + ], +) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9400c2e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,152 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.73" +name = "cxx" +version = "1.0.157" +authors = ["David Tolnay "] +build = "build.rs" +links = "cxxbridge1" +exclude = [ + "/demo", + "/gen", + "/syntax", + "/third-party", + "/tools/buck/prelude", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Safe interop between Rust and C++" +homepage = "https://cxx.rs" +documentation = "https://docs.rs/cxx" +readme = "README.md" +keywords = [ + "ffi", + "c++", +] +categories = [ + "development-tools::ffi", + "api-bindings", + "no-std", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/cxx" + +[package.metadata.bazel] +additive_build_file_content = ''' +cc_library( + name = "cxx_cc", + srcs = ["src/cxx.cc"], + hdrs = ["include/cxx.h"], + include_prefix = "rust", + includes = ["include"], + linkstatic = True, + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) +''' +deps = [":cxx_cc"] +gen_build_script = false + +[package.metadata.bazel.extra_aliased_targets] +cxx_cc = "cxx_cc" + +[package.metadata.docs.rs] +rustdoc-args = [ + "--generate-link-to-definition", + "--extern-html-root-url=core=https://doc.rust-lang.org", + "--extern-html-root-url=alloc=https://doc.rust-lang.org", + "--extern-html-root-url=std=https://doc.rust-lang.org", +] +targets = ["x86_64-unknown-linux-gnu"] + +[features] +alloc = [] +"c++14" = ["cxxbridge-flags/c++14"] +"c++17" = ["cxxbridge-flags/c++17"] +"c++20" = ["cxxbridge-flags/c++20"] +default = [ + "std", + "cxxbridge-flags/default", +] +std = [ + "alloc", + "foldhash/std", +] + +[lib] +name = "cxx" +path = "src/lib.rs" + +[[test]] +name = "compiletest" +path = "tests/compiletest.rs" + +[[test]] +name = "cxx_gen" +path = "tests/cxx_gen.rs" + +[[test]] +name = "cxx_string" +path = "tests/cxx_string.rs" + +[[test]] +name = "cxx_vector" +path = "tests/cxx_vector.rs" + +[[test]] +name = "test" +path = "tests/test.rs" + +[[test]] +name = "unique_ptr" +path = "tests/unique_ptr.rs" + +[dependencies.cxxbridge-macro] +version = "=1.0.157" + +[dependencies.foldhash] +version = "0.1" +default-features = false + +[dependencies.link-cplusplus] +version = "1.0.9" + +[dev-dependencies.cxx-build] +version = "=1.0.157" + +[dev-dependencies.cxx-gen] +version = "0.7" + +[dev-dependencies.cxx-test-suite] +version = "0" + +[dev-dependencies.rustversion] +version = "1.0.13" + +[dev-dependencies.trybuild] +version = "1.0.81" +features = ["diff"] + +[build-dependencies.cc] +version = "1.0.83" + +[build-dependencies.cxxbridge-flags] +version = "=1.0.157" +default-features = false + +[target."cfg(any())".build-dependencies.cxxbridge-cmd] +version = "=1.0.157" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..c67f252 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,76 @@ +[package] +name = "cxx" +version = "1.0.157" +authors = ["David Tolnay "] +categories = ["development-tools::ffi", "api-bindings", "no-std"] +description = "Safe interop between Rust and C++" +documentation = "https://docs.rs/cxx" +edition = "2021" +exclude = ["/demo", "/gen", "/syntax", "/third-party", "/tools/buck/prelude"] +homepage = "https://cxx.rs" +keywords = ["ffi", "c++"] +license = "MIT OR Apache-2.0" +links = "cxxbridge1" +repository = "https://github.com/dtolnay/cxx" +rust-version = "1.73" + +[features] +default = ["std", "cxxbridge-flags/default"] # c++11 +"c++14" = ["cxxbridge-flags/c++14"] +"c++17" = ["cxxbridge-flags/c++17"] +"c++20" = ["cxxbridge-flags/c++20"] +alloc = [] +std = ["alloc", "foldhash/std"] + +[dependencies] +cxxbridge-macro = { version = "=1.0.157", path = "macro" } +foldhash = { version = "0.1", default-features = false } +link-cplusplus = "1.0.9" + +[build-dependencies] +cc = "1.0.83" +cxxbridge-flags = { version = "=1.0.157", path = "flags", default-features = false } + +[dev-dependencies] +cxx-build = { version = "=1.0.157", path = "gen/build" } +cxx-gen = { version = "0.7", path = "gen/lib" } +cxx-test-suite = { version = "0", path = "tests/ffi" } +rustversion = "1.0.13" +trybuild = { version = "1.0.81", features = ["diff"] } + +# Disallow incompatible cxxbridge-cmd version appearing in the same lockfile. +[target.'cfg(any())'.build-dependencies] +cxxbridge-cmd = { version = "=1.0.157", path = "gen/cmd" } + +[workspace] +members = ["demo", "flags", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/ffi"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +rustdoc-args = [ + "--generate-link-to-definition", + "--extern-html-root-url=core=https://doc.rust-lang.org", + "--extern-html-root-url=alloc=https://doc.rust-lang.org", + "--extern-html-root-url=std=https://doc.rust-lang.org", +] + +[package.metadata.bazel] +additive_build_file_content = """ +cc_library( + name = "cxx_cc", + srcs = ["src/cxx.cc"], + hdrs = ["include/cxx.h"], + include_prefix = "rust", + includes = ["include"], + linkstatic = True, + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) +""" +deps = [":cxx_cc"] +extra_aliased_targets = { cxx_cc = "cxx_cc" } +gen_build_script = false + +[patch.crates-io] +cxx = { path = "." } +cxx-build = { path = "gen/build" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +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/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..c14a5c9 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,21 @@ +module( + name = "cxx.rs", + version = "0.0.0", + bazel_compatibility = [">=7.2.1"], + compatibility_level = 1, +) + +bazel_dep(name = "bazel_features", version = "1.21.0") +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "rules_rust", version = "0.60.0") + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain(versions = ["1.86.0"]) +use_repo(rust, "rust_toolchains") + +register_toolchains("@rust_toolchains//:all") + +crate_repositories = use_extension("//tools/bazel:extension.bzl", "crate_repositories") +use_repo(crate_repositories, "crates.io", "vendor") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..e84b165 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,243 @@ +{ + "lockFileVersion": 18, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", + "https://bcr.bazel.build/modules/apple_support/1.17.1/source.json": "6b2b8c74d14e8d485528a938e44bdb72a5ba17632b9e14ef6e68a5ee96c8347f", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/source.json": "3e8379efaaef53ce35b7b8ba419df829315a880cb0a030e5bb45c96d6d5ecb5f", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", + "https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", + "https://bcr.bazel.build/modules/rules_rust/0.60.0/MODULE.bazel": "911ff2a12d01ac574fd6dfec0b05fa976ff8693d8c2420db637a9f98f697b0ae", + "https://bcr.bazel.build/modules/rules_rust/0.60.0/source.json": "2b17f77e27489aa1b86b765a141642a1966a2a35fed0207277f3327fd09ef3d4", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "xcBTf2+GaloFpg7YEh/Bv+1yAczRkiCt3DGws4K7kSk=", + "usagesDigest": "3L+PK6aRnliv0iIS8m3kdo+LjmvjJWoFCm3qZcPSg+8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ] + ] + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c6dab1 --- /dev/null +++ b/README.md @@ -0,0 +1,388 @@ +CXX — safe FFI between Rust and C++ +========================================= + +[github](https://github.com/dtolnay/cxx) +[crates.io](https://crates.io/crates/cxx) +[docs.rs](https://docs.rs/cxx) +[build status](https://github.com/dtolnay/cxx/actions?query=branch%3Amaster) + +This library provides a **safe** mechanism for calling C++ code from Rust and +Rust code from C++, not subject to the many ways that things can go wrong when +using bindgen or cbindgen to generate unsafe C-style bindings. + +This doesn't change the fact that 100% of C++ code is unsafe. When auditing a +project, you would be on the hook for auditing all the unsafe Rust code and +*all* the C++ code. The core safety claim under this new model is that auditing +just the C++ side would be sufficient to catch all problems, i.e. the Rust side +can be 100% safe. + +```toml +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +*Compiler support: requires rustc 1.73+ and c++11 or newer*
+*[Release notes](https://github.com/dtolnay/cxx/releases)* + +
+ +## Guide + +Please see **** for a tutorial, reference material, and example +code. + +
+ +## Overview + +The idea is that we define the signatures of both sides of our FFI boundary +embedded together in one Rust module (the next section shows an example). From +this, CXX receives a complete picture of the boundary to perform static analyses +against the types and function signatures to uphold both Rust's and C++'s +invariants and requirements. + +If everything checks out statically, then CXX uses a pair of code generators to +emit the relevant `extern "C"` signatures on both sides together with any +necessary static assertions for later in the build process to verify +correctness. On the Rust side this code generator is simply an attribute +procedural macro. On the C++ side it can be a small Cargo build script if your +build is managed by Cargo, or for other build systems like Bazel or Buck we +provide a command line tool which generates the header and source file and +should be easy to integrate. + +The resulting FFI bridge operates at zero or negligible overhead, i.e. no +copying, no serialization, no memory allocation, no runtime checks needed. + +The FFI signatures are able to use native types from whichever side they please, +such as Rust's `String` or C++'s `std::string`, Rust's `Box` or C++'s +`std::unique_ptr`, Rust's `Vec` or C++'s `std::vector`, etc in any combination. +CXX guarantees an ABI-compatible signature that both sides understand, based on +builtin bindings for key standard library types to expose an idiomatic API on +those types to the other language. For example when manipulating a C++ string +from Rust, its `len()` method becomes a call of the `size()` member function +defined by C++; when manipulating a Rust string from C++, its `size()` member +function calls Rust's `len()`. + +
+ +## Example + +In this example we are writing a Rust application that wishes to take advantage +of an existing C++ client for a large-file blobstore service. The blobstore +supports a `put` operation for a discontiguous buffer upload. For example we +might be uploading snapshots of a circular buffer which would tend to consist of +2 chunks, or fragments of a file spread across memory for some other reason. + +A runnable version of this example is provided under the *demo* directory of +this repo. To try it out, run `cargo run` from that directory. + +```rust +#[cxx::bridge] +mod ffi { + // Any shared structs, whose fields will be visible to both languages. + struct BlobMetadata { + size: usize, + tags: Vec, + } + + extern "Rust" { + // Zero or more opaque types which both languages can pass around but + // only Rust can see the fields. + type MultiBuf; + + // Functions implemented in Rust. + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + // One or more headers with the matching C++ declarations. Our code + // generators don't read it but it gets #include'd and used in static + // assertions to ensure our picture of the FFI boundary is accurate. + include!("demo/include/blobstore.h"); + + // Zero or more opaque types which both languages can pass around but + // only C++ can see the fields. + type BlobstoreClient; + + // Functions implemented in C++. + fn new_blobstore_client() -> UniquePtr; + fn put(&self, parts: &mut MultiBuf) -> u64; + fn tag(&self, blobid: u64, tag: &str); + fn metadata(&self, blobid: u64) -> BlobMetadata; + } +} +``` + +Now we simply provide Rust definitions of all the things in the `extern "Rust"` +block and C++ definitions of all the things in the `extern "C++"` block, and get +to call back and forth safely. + +Here are links to the complete set of source files involved in the demo: + +- [demo/src/main.rs](demo/src/main.rs) +- [demo/build.rs](demo/build.rs) +- [demo/include/blobstore.h](demo/include/blobstore.h) +- [demo/src/blobstore.cc](demo/src/blobstore.cc) + +To look at the code generated in both languages for the example by the CXX code +generators: + +```console + # run Rust code generator and print to stdout + # (requires https://github.com/dtolnay/cargo-expand) +$ cargo expand --manifest-path demo/Cargo.toml + + # run C++ code generator and print to stdout +$ cargo run --manifest-path gen/cmd/Cargo.toml -- demo/src/main.rs +``` + +
+ +## Details + +As seen in the example, the language of the FFI boundary involves 3 kinds of +items: + +- **Shared structs** — their fields are made visible to both languages. + The definition written within cxx::bridge is the single source of truth. + +- **Opaque types** — their fields are secret from the other language. + These cannot be passed across the FFI by value but only behind an indirection, + such as a reference `&`, a Rust `Box`, or a `UniquePtr`. Can be a type alias + for an arbitrarily complicated generic language-specific type depending on + your use case. + +- **Functions** — implemented in either language, callable from the other + language. + +Within the `extern "Rust"` part of the CXX bridge we list the types and +functions for which Rust is the source of truth. These all implicitly refer to +the `super` module, the parent module of the CXX bridge. You can think of the +two items listed in the example above as being like `use super::MultiBuf` and +`use super::next_chunk` except re-exported to C++. The parent module will either +contain the definitions directly for simple things, or contain the relevant +`use` statements to bring them into scope from elsewhere. + +Within the `extern "C++"` part, we list types and functions for which C++ is the +source of truth, as well as the header(s) that declare those APIs. In the future +it's possible that this section could be generated bindgen-style from the +headers but for now we need the signatures written out; static assertions will +verify that they are accurate. + +Your function implementations themselves, whether in C++ or Rust, *do not* need +to be defined as `extern "C"` ABI or no\_mangle. CXX will put in the right shims +where necessary to make it all work. + +
+ +## Comparison vs bindgen and cbindgen + +Notice that with CXX there is repetition of all the function signatures: they +are typed out once where the implementation is defined (in C++ or Rust) and +again inside the cxx::bridge module, though compile-time assertions guarantee +these are kept in sync. This is different from [bindgen] and [cbindgen] where +function signatures are typed by a human once and the tool consumes them in one +language and emits them in the other language. + +[bindgen]: https://github.com/rust-lang/rust-bindgen +[cbindgen]: https://github.com/eqrion/cbindgen/ + +This is because CXX fills a somewhat different role. It is a lower level tool +than bindgen or cbindgen in a sense; you can think of it as being a replacement +for the concept of `extern "C"` signatures as we know them, rather than a +replacement for a bindgen. It would be reasonable to build a higher level +bindgen-like tool on top of CXX which consumes a C++ header and/or Rust module +(and/or IDL like Thrift) as source of truth and generates the cxx::bridge, +eliminating the repetition while leveraging the static analysis safety +guarantees of CXX. + +But note in other ways CXX is higher level than the bindgens, with rich support +for common standard library types. Frequently with bindgen when we are dealing +with an idiomatic C++ API we would end up manually wrapping that API in C-style +raw pointer functions, applying bindgen to get unsafe raw pointer Rust +functions, and replicating the API again to expose those idiomatically in Rust. +That's a much worse form of repetition because it is unsafe all the way through. + +By using a CXX bridge as the shared understanding between the languages, rather +than `extern "C"` C-style signatures as the shared understanding, common FFI use +cases become expressible using 100% safe code. + +It would also be reasonable to mix and match, using CXX bridge for the 95% of +your FFI that is straightforward and doing the remaining few oddball signatures +the old fashioned way with bindgen and cbindgen, if for some reason CXX's static +restrictions get in the way. Please file an issue if you end up taking this +approach so that we know what ways it would be worthwhile to make the tool more +expressive. + +
+ +## Cargo-based setup + +For builds that are orchestrated by Cargo, you will use a build script that runs +CXX's C++ code generator and compiles the resulting C++ code along with any +other C++ code for your crate. + +The canonical build script is as follows. The indicated line returns a +[`cc::Build`] instance (from the usual widely used `cc` crate) on which you can +set up any additional source files and compiler flags as normal. + +[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html + +```toml +# Cargo.toml + +[build-dependencies] +cxx-build = "1.0" +``` + +```rust +// build.rs + +fn main() { + cxx_build::bridge("src/main.rs") // returns a cc::Build + .file("src/demo.cc") + .std("c++11") + .compile("cxxbridge-demo"); + + println!("cargo:rerun-if-changed=src/main.rs"); + println!("cargo:rerun-if-changed=src/demo.cc"); + println!("cargo:rerun-if-changed=include/demo.h"); +} +``` + +
+ +## Non-Cargo setup + +For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate way of +invoking the C++ code generator as a standalone command line tool. The tool is +packaged as the `cxxbridge-cmd` crate on crates.io or can be built from the +*gen/cmd* directory of this repo. + +```bash +$ cargo install cxxbridge-cmd + +$ cxxbridge src/main.rs --header > path/to/mybridge.h +$ cxxbridge src/main.rs > path/to/mybridge.cc +``` + +
+ +## Safety + +Be aware that the design of this library is intentionally restrictive and +opinionated! It isn't a goal to be powerful enough to handle arbitrary +signatures in either language. Instead this project is about carving out a +reasonably expressive set of functionality about which we can make useful safety +guarantees today and maybe extend over time. You may find that it takes some +practice to use CXX bridge effectively as it won't work in all the ways that you +are used to. + +Some of the considerations that go into ensuring safety are: + +- By design, our paired code generators work together to control both sides of + the FFI boundary. Ordinarily in Rust writing your own `extern "C"` blocks is + unsafe because the Rust compiler has no way to know whether the signatures + you've written actually match the signatures implemented in the other + language. With CXX we achieve that visibility and know what's on the other + side. + +- Our static analysis detects and prevents passing types by value that shouldn't + be passed by value from C++ to Rust, for example because they may contain + internal pointers that would be screwed up by Rust's move behavior. + +- To many people's surprise, it is possible to have a struct in Rust and a + struct in C++ with exactly the same layout / fields / alignment / everything, + and still not the same ABI when passed by value. This is a longstanding + bindgen bug that leads to segfaults in absolutely correct-looking code + ([rust-lang/rust-bindgen#778]). CXX knows about this and can insert the + necessary zero-cost workaround transparently where needed, so go ahead and + pass your structs by value without worries. This is made possible by owning + both sides of the boundary rather than just one. + +- Template instantiations: for example in order to expose a UniquePtr\ type + in Rust backed by a real C++ unique\_ptr, we have a way of using a Rust trait + to connect the behavior back to the template instantiations performed by the + other language. + +[rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778 + +
+ +## Builtin types + +In addition to all the primitive types (i32 <=> int32_t), the following +common types may be used in the fields of shared structs and the arguments and +returns of functions. + + + + + + + + + + + + + + + + + +
name in Rustname in C++restrictions
Stringrust::String
&strrust::Str
&[T]rust::Slice<const T>cannot hold opaque C++ type
&mut [T]rust::Slice<T>cannot hold opaque C++ type
CxxStringstd::stringcannot be passed by value
Box<T>rust::Box<T>cannot hold opaque C++ type
UniquePtr<T>std::unique_ptr<T>cannot hold opaque Rust type
SharedPtr<T>std::shared_ptr<T>cannot hold opaque Rust type
[T; N]std::array<T, N>cannot hold opaque C++ type
Vec<T>rust::Vec<T>cannot hold opaque C++ type
CxxVector<T>std::vector<T>cannot be passed by value, cannot hold opaque Rust type
*mut T, *const TT*, const T*fn with a raw pointer argument must be declared unsafe to call
fn(T, U) -> Vrust::Fn<V(T, U)>only passing from Rust to C++ is implemented so far
Result<T>throw/catchallowed as return type only
+ +The C++ API of the `rust` namespace is defined by the *include/cxx.h* file in +this repo. You will need to include this header in your C++ code when working +with those types. + +The following types are intended to be supported "soon" but are just not +implemented yet. I don't expect any of these to be hard to make work but it's a +matter of designing a nice API for each in its non-native language. + + + + + + + + + +
name in Rustname in C++
BTreeMap<K, V>tbd
HashMap<K, V>tbd
Arc<T>tbd
Option<T>tbd
tbdstd::map<K, V>
tbdstd::unordered_map<K, V>
+ +
+ +## Remaining work + +This is still early days for CXX; I am releasing it as a minimum viable product +to collect feedback on the direction and invite collaborators. Please check the +open issues. + +Especially please report issues if you run into trouble building or linking any +of this stuff. I'm sure there are ways to make the build aspects friendlier or +more robust. + +Finally, I know more about Rust library design than C++ library design so I +would appreciate help making the C++ APIs in this project more idiomatic where +anyone has suggestions. + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this project by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. + diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..3c7d187 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1,3 @@ +/build/ +/mdbook +/node_modules/ diff --git a/book/README.md b/book/README.md new file mode 100644 index 0000000..e4916e0 --- /dev/null +++ b/book/README.md @@ -0,0 +1,9 @@ +Published automatically to https://cxx.rs from master branch. + +To build and view locally: + +- Install [mdBook]: `cargo install mdbook`. +- Run `mdbook build` in this directory. +- Open the generated *build/index.html*. + +[mdBook]: https://github.com/rust-lang/mdBook diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000..a8148fe --- /dev/null +++ b/book/book.toml @@ -0,0 +1,22 @@ +[book] +#title = "Rust ♡ C++" +authors = ["David Tolnay"] +description = "CXX — safe interop between Rust and C++ by David Tolnay. This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++." + +[rust] +edition = "2021" + +[build] +build-dir = "build" +create-missing = false + +[output.html] +additional-css = ["css/cxx.css"] +cname = "cxx.rs" +git-repository-url = "https://github.com/dtolnay/cxx" +playground = { copyable = false } +print = { enable = false } + +[output.html.redirect] +"binding/index.html" = "../bindings.html" +"build/index.html" = "../building.html" diff --git a/book/build.js b/book/build.js new file mode 100755 index 0000000..85da7bf --- /dev/null +++ b/book/build.js @@ -0,0 +1,137 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const cheerio = require('cheerio'); +const entities = require('html-entities'); +const hljs = require('./build/highlight.js'); + +const githublink = `\ +
  • \ +\ +\ +https://github.com/dtolnay/cxx\ +\ +
  • `; + +const opengraph = `\ +\ +\ +\ +\ +\ +\ +`; + +const themejs = `\ +var theme; +try { theme = localStorage.getItem('mdbook-theme'); } catch(e) {} +if (theme === null || theme === undefined) { theme = default_theme; } +const html = document.documentElement; +html.classList.remove('light') +html.classList.add(theme); +html.classList.add("js");`; + +const themejsReplacement = `\ +const html = document.documentElement; +html.classList.add('js');`; + +const dirs = ['build']; +while (dirs.length) { + const dir = dirs.pop(); + fs.readdirSync(dir).forEach((entry) => { + const path = dir + '/' + entry; + const stat = fs.statSync(path); + if (stat.isDirectory()) { + dirs.push(path); + return; + } + + if (!path.endsWith('.html')) { + return; + } + + const index = fs.readFileSync(path, 'utf8'); + const $ = cheerio.load(index, { + decodeEntities: false, + xml: { xmlMode: false }, + }); + + $('head').append(opengraph); + $('nav#sidebar ol.chapter').append(githublink); + $('head link[href="tomorrow-night.css"]').attr('disabled', true); + $('head link[href="ayu-highlight.css"]').attr('disabled', true); + $('button#theme-toggle').attr('style', 'display:none'); + $('pre code').each(function () { + const node = $(this); + const langClass = node.attr('class').split(' ', 2)[0]; + if (!langClass.startsWith('language-')) { + return; + } + const lang = langClass.replace('language-', ''); + const originalLines = node.html().split('\n'); + const boring = originalLines.map((line) => + line.includes(''), + ); + const ellipsis = originalLines.map((line) => line.includes('// ...')); + const target = entities.decode(node.text()); + const highlightedLines = hljs.highlight(lang, target).value.split('\n'); + const result = highlightedLines + .map(function (line, i) { + if (boring[i]) { + line = '' + line; + } else if (ellipsis[i]) { + line = '' + line; + } + if (i > 0 && (boring[i - 1] || ellipsis[i - 1])) { + line = '' + line; + } + if (i + 1 === highlightedLines.length && (boring[i] || ellipsis[i])) { + line = line + ''; + } + return line; + }) + .join('\n'); + node.text(result); + node.removeClass(langClass); + if (!node.hasClass('focuscomment')) { + node.addClass('hidelines'); + node.addClass('hide-boring'); + } + }); + $('code').each(function () { + $(this).addClass('hljs'); + }); + + var foundScript = false; + $('body script').each(function () { + const node = $(this); + if (node.text().replace(/\s/g, '') === themejs.replace(/\s/g, '')) { + node.text(themejsReplacement); + foundScript = true; + } + }); + const pathsWithoutScript = [ + 'build/toc.html', + 'build/build/index.html', + 'build/binding/index.html', + ]; + if (!foundScript && !pathsWithoutScript.includes(path)) { + throw new Error('theme script not found'); + } + + const out = $.html(); + fs.writeFileSync(path, out); + }); +} + +fs.copyFileSync('build/highlight.css', 'build/tomorrow-night.css'); +fs.copyFileSync('build/highlight.css', 'build/ayu-highlight.css'); + +var bookjs = fs.readFileSync('build/book.js', 'utf8'); +bookjs = bookjs + .replace('set_theme(theme, false);', '') + .replace( + 'document.querySelectorAll("code.hljs")', + 'document.querySelectorAll("code.hidelines")', + ); +fs.writeFileSync('build/book.js', bookjs); diff --git a/book/build.sh b/book/build.sh new file mode 100755 index 0000000..783d304 --- /dev/null +++ b/book/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")" + +if [ -f ./mdbook ]; then + ./mdbook build +else + mdbook build +fi + +if [ ! -d node_modules ]; then + npm install +fi + +./build.js diff --git a/book/css/cxx.css b/book/css/cxx.css new file mode 100644 index 0000000..68d32db --- /dev/null +++ b/book/css/cxx.css @@ -0,0 +1,49 @@ +:root { + --sidebar-width: 310px; +} + +.badges img { + margin: 0 7px 7px 0; +} + +.badges { + margin: 16px 0 120px; +} + +.boring { + opacity: 0.5; +} + +.no-js code:not(.focuscomment) .boring { + display: none; +} + +.js code:not(.hide-boring) .ellipsis { + display: none; +} + +.focuscomment .hljs-comment { + font-weight: bold; + color: black; +} + +.focuscomment .boring { + opacity: 0.5; +} + +nav.sidebar li.part-title i.fa-github { + font-size: 20px; + padding-right: 5px; + padding-top: 12px; + position: relative; + top: 1px; +} + +.sidebar .sidebar-scrollbox { + padding: 10px 0 10px 10px; +} + +pre > .buttons { + visibility: visible; + opacity: 0.3; +} diff --git a/book/diagram/.gitignore b/book/diagram/.gitignore new file mode 100644 index 0000000..0017281 --- /dev/null +++ b/book/diagram/.gitignore @@ -0,0 +1,7 @@ +/*.aux +/*.fdb_latexmk +/*.fls +/*.log +/*.pdf +/*.png +/*.svg diff --git a/book/diagram/Makefile b/book/diagram/Makefile new file mode 100644 index 0000000..1723a9b --- /dev/null +++ b/book/diagram/Makefile @@ -0,0 +1,8 @@ +overview.svg: overview.pdf + pdf2svg $< $@ + +overview.pdf: overview.tex + latexmk $< + +overview.png: overview.svg + svgexport $< $@ 3x diff --git a/book/diagram/overview.tex b/book/diagram/overview.tex new file mode 100644 index 0000000..a613bb7 --- /dev/null +++ b/book/diagram/overview.tex @@ -0,0 +1,45 @@ +\documentclass{standalone} +\usepackage{makecell} +\usepackage{pgfplots} +\usepackage{sansmath} +\usetikzlibrary{arrows.meta} +\pgfplotsset{compat=1.16} +\begin{document} +\pagecolor{white} +\begin{tikzpicture}[ + x=1cm, + y=-.6cm, + every node/.append style={ + line width=1.5pt, + font=\Large\sansmath\sffamily, + }, + every path/.append style={ + >={Latex[length=10pt,width=8pt]}, + line width=1.5pt, + }, + execute at end node={\vphantom{bg}}, +] +\node[draw, rounded corners=5, inner xsep=30pt, inner ysep=2pt] + (bridge) at (0, .25) {\makecell{\texttt{\#\hspace{-1pt}[}cxx::bridge\texttt{]} mod\\[-4pt]description of boundary}}; +\node[draw, rounded corners, inner xsep=10pt, inner ysep=6pt, text depth=1pt] + (rust-bindings) at (-3.5, 6.5) {Rust bindings}; +\node[draw, rounded corners, inner xsep=10pt, inner ysep=6pt, text depth=1pt] + (cpp-bindings) at (3.5, 6.5) {C\texttt{++} bindings}; +\node[inner xsep=4pt, inner ysep=-0pt] + (rust-code) at (-9, 6.5) {\makecell[r]{\\[-8pt]Rust\\[-4pt]code}}; +\node[inner xsep=4pt, inner ysep=-0pt] + (cpp-code) at (9, 6.5) {\makecell[l]{\\[-8pt]C\texttt{++}\\[-4pt]code}}; +\draw (bridge) -- (0, 4); +\draw[<->] (rust-bindings) |- (0, 4) -| (cpp-bindings); +\draw[<->] (rust-code) -- (rust-bindings); +\draw[<->, dash pattern=on 8pt off 6pt] (rust-bindings) -- (cpp-bindings); +\draw[<->] (cpp-bindings) -- (cpp-code); +\draw (-.75, 4) node[anchor=south east] {Macro expansion}; +\draw (.75, 4) node[anchor=south west] {Code generation}; +\draw (0, 6.5) node[anchor=south, inner ysep=4pt] {Hidden C ABI}; +\draw (-6.75, 6.5) node[anchor=south, inner ysep=1pt] {\makecell{Safe\\[-4pt]straightforward\\[-4pt]Rust APIs}}; +\draw (6.75, 6.5) node[anchor=south, inner ysep=1pt] {\makecell{Straightforward\\[-4pt]C\texttt{++} APIs}}; +\pgfresetboundingbox\path + (-9.5, 0) -- (rust-bindings.south)+(0, .3) -- (9.5, 0) -- (bridge.north); +\end{tikzpicture} +\end{document} diff --git a/book/eslint.config.mjs b/book/eslint.config.mjs new file mode 100644 index 0000000..6ad9c6c --- /dev/null +++ b/book/eslint.config.mjs @@ -0,0 +1,8 @@ +import pluginJs from '@eslint/js'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: ['build/*'] }, + { files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, + pluginJs.configs.recommended, +]; diff --git a/book/package-lock.json b/book/package-lock.json new file mode 100644 index 0000000..a6af7c2 --- /dev/null +++ b/book/package-lock.json @@ -0,0 +1,1365 @@ +{ + "name": "cxx-book-build", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cxx-book-build", + "version": "0.0.0", + "dependencies": { + "cheerio": "^1.0.0", + "html-entities": "^2.5.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "eslint": "^9.19.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", + "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", + "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.19.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/book/package.json b/book/package.json new file mode 100644 index 0000000..bbed765 --- /dev/null +++ b/book/package.json @@ -0,0 +1,16 @@ +{ + "name": "cxx-book-build", + "version": "0.0.0", + "main": "build.js", + "dependencies": { + "cheerio": "^1.0.0", + "html-entities": "^2.5.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "eslint": "^9.19.0" + }, + "prettier": { + "singleQuote": true + } +} diff --git a/book/src/404.md b/book/src/404.md new file mode 100644 index 0000000..c5b71f2 --- /dev/null +++ b/book/src/404.md @@ -0,0 +1,5 @@ +### Whoops, this page doesn’t exist :-( + +
    + +ferris diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..2d2502e --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,37 @@ +# Summary + +- [Rust ❤️ C++](index.md) + +- [Core concepts](concepts.md) + +- [Tutorial](tutorial.md) + +- [Other Rust–C++ interop tools](context.md) + +- [Multi-language build system options](building.md) + - [Cargo](build/cargo.md) + - [Bazel or Buck2](build/bazel.md) + - [CMake](build/cmake.md) + - [More...](build/other.md) + +- [Reference: the bridge module](reference.md) + - [extern "Rust"](extern-rust.md) + - [extern "C++"](extern-c++.md) + - [Shared types](shared.md) + - [Attributes](attributes.md) + - [Async functions](async.md) + - [Error handling](binding/result.md) + +- [Reference: built-in bindings](bindings.md) + - [String — rust::String](binding/string.md) + - [&str — rust::Str](binding/str.md) + - [&[T], &mut [T] — rust::Slice\](binding/slice.md) + - [CxxString — std::string](binding/cxxstring.md) + - [Box\ — rust::Box\](binding/box.md) + - [UniquePtr\ — std::unique\_ptr\](binding/uniqueptr.md) + - [SharedPtr\ — std::shared\_ptr\](binding/sharedptr.md) + - [Vec\ — rust::Vec\](binding/vec.md) + - [CxxVector\ — std::vector\](binding/cxxvector.md) + - [*mut T, *const T raw pointers](binding/rawptr.md) + - [Function pointers](binding/fn.md) + - [Result\](binding/result.md) diff --git a/book/src/async.md b/book/src/async.md new file mode 100644 index 0000000..0f3fed1 --- /dev/null +++ b/book/src/async.md @@ -0,0 +1,86 @@ +{{#title Async functions — Rust ♡ C++}} +# Async functions + +Direct FFI of async functions is absolutely in scope for CXX (on C++20 and up) +but is not implemented yet in the current release. We are aiming for an +implementation that is as easy as: + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + async fn doThing(arg: Arg) -> Ret; + } +} +``` + +```cpp +rust::Future doThing(Arg arg) { + auto v1 = co_await f(); + auto v2 = co_await g(arg); + co_return v1 + v2; +} +``` + +## Workaround + +For now the recommended approach is to handle the return codepath over a oneshot +channel (such as [`futures::channel::oneshot`]) represented in an opaque Rust +type on the FFI. + +[`futures::channel::oneshot`]: https://docs.rs/futures/0.3.8/futures/channel/oneshot/index.html + +```rust,noplayground +// bridge.rs + +use futures::channel::oneshot; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type DoThingContext; + } + + unsafe extern "C++" { + include!("path/to/bridge_shim.h"); + + fn shim_doThing( + arg: Arg, + done: fn(Box, ret: Ret), + ctx: Box, + ); + } +} + +struct DoThingContext(oneshot::Sender); + +pub async fn do_thing(arg: Arg) -> Ret { + let (tx, rx) = oneshot::channel(); + let context = Box::new(DoThingContext(tx)); + + ffi::shim_doThing( + arg, + |context, ret| { let _ = context.0.send(ret); }, + context, + ); + + rx.await.unwrap() +} +``` + +```cpp +// bridge_shim.cc + +#include "path/to/bridge.rs.h" +#include "rust/cxx.h" + +void shim_doThing( + Arg arg, + rust::Fn ctx, Ret ret)> done, + rust::Box ctx) noexcept { + doThing(arg) + .then([done, ctx(std::move(ctx))](auto &&res) mutable { + (*done)(std::move(ctx), std::move(res)); + }); +} +``` diff --git a/book/src/attributes.md b/book/src/attributes.md new file mode 100644 index 0000000..9c33b77 --- /dev/null +++ b/book/src/attributes.md @@ -0,0 +1,75 @@ +{{#title Attributes — Rust ♡ C++}} +# Attributes + +## namespace + +The top-level cxx::bridge attribute macro takes an optional `namespace` argument +to control the C++ namespace into which to emit extern Rust items and the +namespace in which to expect to find the extern C++ items. + +```rust,noplayground +#[cxx::bridge(namespace = "path::of::my::company")] +mod ffi { + extern "Rust" { + type MyType; // emitted to path::of::my::company::MyType + } + + extern "C++" { + type TheirType; // refers to path::of::my::company::TheirType + } +} +``` + +Additionally, a `#[namespace = "..."]` attribute may be used inside the bridge +module on any extern block or individual item. An item will inherit the +namespace specified on its surrounding extern block if any, otherwise the +namespace specified with the top level cxx::bridge attribute if any, otherwise +the global namespace. + +```rust,noplayground +#[cxx::bridge(namespace = "third_priority")] +mod ffi { + #[namespace = "second_priority"] + extern "Rust" { + fn f(); + + #[namespace = "first_priority"] + fn g(); + } + + extern "Rust" { + fn h(); + } +} +``` + +The above would result in functions `::second_priority::f`, +`::first_priority::g`, `::third_priority::h`. + +## rust\_name, cxx\_name + +Sometimes you want the Rust name of a function or type to differ from its C++ +name. Importantly, this enables binding multiple overloads of the same C++ +function name using distinct Rust names. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + #[rust_name = "i32_overloaded_function"] + fn cOverloadedFunction(x: i32) -> String; + #[rust_name = "str_overloaded_function"] + fn cOverloadedFunction(x: &str) -> String; + } +} +``` + +The `#[rust_name = "..."]` attribute replaces the name that Rust should use for +this function, and an analogous `#[cxx_name = "..."]` attribute replaces the +name that C++ should use. + +Either of the two attributes may be used on extern "Rust" as well as extern +"C++" functions, according to which one you find clearer in context. + +The same attribute works for renaming functions, opaque types, shared +structs and enums, and enum variants. diff --git a/book/src/binding/box.md b/book/src/binding/box.md new file mode 100644 index 0000000..abd40d6 --- /dev/null +++ b/book/src/binding/box.md @@ -0,0 +1,120 @@ +{{#title rust::Box — Rust ♡ C++}} +# rust::Box\ + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...#include +... +...namespace rust { + +template +class Box final { +public: + using element_type = T; + using const_pointer = + typename std::add_pointer::type>::type; + using pointer = typename std::add_pointer::type; + + Box(Box &&) noexcept; + ~Box() noexcept; + + explicit Box(const T &); + explicit Box(T &&); + + Box &operator=(Box &&) & noexcept; + + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; + + template + static Box in_place(Fields &&...); + + void swap(Box &) noexcept; + + // Important: requires that `raw` came from an into_raw call. Do not + // pass a pointer from `new` or any other source. + static Box from_raw(T *) noexcept; + + T *into_raw() noexcept; +}; +... +...} // namespace rust +``` + +### Restrictions: + +Box\ does not support T being an opaque C++ type. You should use +[UniquePtr\](uniqueptr.md) or [SharedPtr\](sharedptr.md) instead for +transferring ownership of opaque C++ types on the language boundary. + +If T is an opaque Rust type, the Rust type is required to be [Sized] i.e. size +known at compile time. In the future we may introduce support for dynamically +sized opaque Rust types. + +[Sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html + +## Example + +This program uses a Box to pass ownership of some opaque piece of Rust state +over to C++ and then back to a Rust callback, which is a useful pattern for +implementing [async functions over FFI](../async.md). + +```rust,noplayground +// src/main.rs + +use std::io::Write; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type File; + } + + unsafe extern "C++" { + include!("example/include/example.h"); + + fn f( + callback: fn(Box, fst: &str, snd: &str), + out: Box, + ); + } +} + +pub struct File(std::fs::File); + +fn main() { + let out = std::fs::File::create("example.log").unwrap(); + + ffi::f( + |mut out, fst, snd| { let _ = write!(out.0, "{}{}\n", fst, snd); }, + Box::new(File(out)), + ); +} +``` + +```cpp +// include/example.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void f(rust::Fn, rust::Str, rust::Str)> callback, + rust::Box out); +``` + +```cpp +// include/example.cc + +#include "example/include/example.h" + +void f(rust::Fn, rust::Str, rust::Str)> callback, + rust::Box out) { + callback(std::move(out), "fearless", "concurrency"); +} +``` diff --git a/book/src/binding/cxxstring.md b/book/src/binding/cxxstring.md new file mode 100644 index 0000000..dc2619c --- /dev/null +++ b/book/src/binding/cxxstring.md @@ -0,0 +1,140 @@ +{{#title std::string — Rust ♡ C++}} +# std::string + +The Rust binding of std::string is called **[`CxxString`]**. See the link for +documentation of the Rust API. + +[`CxxString`]: https://docs.rs/cxx/*/cxx/struct.CxxString.html + +### Restrictions: + +Rust code can never obtain a CxxString by value. C++'s string requires a move +constructor and may hold internal pointers, which is not compatible with Rust's +move behavior. Instead in Rust code we will only ever look at a CxxString +through a reference or smart pointer, as in &CxxString or Pin\<&mut CxxString\> +or UniquePtr\. + +In order to construct a CxxString on the stack from Rust, you must use the +[`let_cxx_string!`] macro which will pin the string properly. The code below +uses this in one place, and the link covers the syntax. + +[`let_cxx_string!`]: https://docs.rs/cxx/*/cxx/macro.let_cxx_string.html + +## Example + +This example uses C++17's std::variant to build a toy JSON type. JSON can hold +various types including strings, and JSON's object type is a map with string +keys. The example demonstrates Rust indexing into one of those maps. + +```rust,noplayground +// src/main.rs + +use cxx::let_cxx_string; + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/json.h"); + + #[cxx_name = "json"] + type Json; + #[cxx_name = "object"] + type Object; + + fn isNull(self: &Json) -> bool; + fn isNumber(self: &Json) -> bool; + fn isString(self: &Json) -> bool; + fn isArray(self: &Json) -> bool; + fn isObject(self: &Json) -> bool; + + fn getNumber(self: &Json) -> f64; + fn getString(self: &Json) -> &CxxString; + fn getArray(self: &Json) -> &CxxVector; + fn getObject(self: &Json) -> &Object; + + #[cxx_name = "at"] + fn get<'a>(self: &'a Object, key: &CxxString) -> &'a Json; + + fn load_config() -> UniquePtr; + } +} + +fn main() { + let config = ffi::load_config(); + + let_cxx_string!(key = "name"); + println!("{}", config.getObject().get(&key).getString()); +} +``` + +```cpp +// include/json.h + +#pragma once +#include +#include +#include +#include +#include + +class json final { +public: + static const json null; + using number = double; + using string = std::string; + using array = std::vector; + using object = std::map; + + json() noexcept = default; + json(const json &) = default; + json(json &&) = default; + template + json(T &&...value) : value(std::forward(value)...) {} + + bool isNull() const; + bool isNumber() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + number getNumber() const; + const string &getString() const; + const array &getArray() const; + const object &getObject() const; + +private: + std::variant value; +}; + +using object = json::object; + +std::unique_ptr load_config(); +``` + +```cpp +// include/json.cc + +#include "example/include/json.h" +#include +#include + +const json json::null{}; +bool json::isNull() const { return std::holds_alternative(value); } +bool json::isNumber() const { return std::holds_alternative(value); } +bool json::isString() const { return std::holds_alternative(value); } +bool json::isArray() const { return std::holds_alternative(value); } +bool json::isObject() const { return std::holds_alternative(value); } +json::number json::getNumber() const { return std::get(value); } +const json::string &json::getString() const { return std::get(value); } +const json::array &json::getArray() const { return std::get(value); } +const json::object &json::getObject() const { return std::get(value); } + +std::unique_ptr load_config() { + return std::make_unique( + std::in_place_type, + std::initializer_list>{ + {"name", "cxx-example"}, + {"edition", 2021.}, + {"repository", json::null}}); +} +``` diff --git a/book/src/binding/cxxvector.md b/book/src/binding/cxxvector.md new file mode 100644 index 0000000..fd95a2d --- /dev/null +++ b/book/src/binding/cxxvector.md @@ -0,0 +1,62 @@ +{{#title std::vector — Rust ♡ C++}} +# std::vector\ + +The Rust binding of std::vector\ is called **[`CxxVector`]**. See the +link for documentation of the Rust API. + +[`CxxVector`]: https://docs.rs/cxx/*/cxx/struct.CxxVector.html + +### Restrictions: + +Rust code can never obtain a CxxVector by value. Instead in Rust code we will +only ever look at a vector behind a reference or smart pointer, as in +&CxxVector\ or UniquePtr\\>. + +CxxVector\ does not support T being an opaque Rust type. You should use a +Vec\ (C++ rust::Vec\) instead for collections of opaque Rust types on +the language boundary. + +## Example + +This program involves Rust code converting a `CxxVector` (i.e. +`std::vector`) into a Rust `Vec`. + +```rust,noplayground +// src/main.rs + +#![no_main] // main defined in C++ by main.cc + +use cxx::{CxxString, CxxVector}; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn f(vec: &CxxVector); + } +} + +fn f(vec: &CxxVector) { + let vec: Vec = vec + .iter() + .map(|s| s.to_string_lossy().into_owned()) + .collect(); + g(&vec); +} + +fn g(vec: &[String]) { + println!("{:?}", vec); +} +``` + +```cpp +// src/main.cc + +#include "example/src/main.rs.h" +#include +#include + +int main() { + std::vector vec{"fearless", "concurrency"}; + f(vec); +} +``` diff --git a/book/src/binding/fn.md b/book/src/binding/fn.md new file mode 100644 index 0000000..a32ad52 --- /dev/null +++ b/book/src/binding/fn.md @@ -0,0 +1,34 @@ +{{#title Function pointers — Rust ♡ C++}} +# Function pointers + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...namespace rust { + +template +class Fn; + +template +class Fn final { +public: + Ret operator()(Args... args) const noexcept; + Fn operator*() const noexcept; +}; +... +...} // namespace rust +``` + +### Restrictions: + +Function pointers with a Result return type are not implemented yet. + +Passing a function pointer from C++ to Rust is not implemented yet, only from +Rust to an `extern "C++"` function is implemented. + +## Example + +Function pointers are commonly useful for implementing [async functions over +FFI](../async.md). See the example code on that page. diff --git a/book/src/binding/rawptr.md b/book/src/binding/rawptr.md new file mode 100644 index 0000000..1794211 --- /dev/null +++ b/book/src/binding/rawptr.md @@ -0,0 +1,100 @@ +{{#title *mut T, *const T — Rust ♡ C++}} +# *mut T, *const T + +Generally you should use references (`&mut T`, `&T`) or [std::unique_ptr\] +where possible over raw pointers, but raw pointers are available too as an +unsafe fallback option. + +[std::unique_ptr\]: uniqueptr.md + +### Restrictions: + +Extern functions and function pointers taking a raw pointer as an argument must +be declared `unsafe fn` i.e. unsafe to call. The same does not apply to +functions which only *return* a raw pointer, though presumably doing anything +useful with the returned pointer is going to involve unsafe code elsewhere +anyway. + +## Example + +This example illustrates making a Rust call to a canonical C-style `main` +signature involving `char *argv[]`. + +```cpp +// include/args.h + +#pragma once + +void parseArgs(int argc, char *argv[]); +``` + +```cpp +// src/args.cc + +#include "example/include/args.h" +#include + +void parseArgs(int argc, char *argv[]) { + std::cout << argc << std::endl; + for (int i = 0; i < argc; i++) { + std::cout << '"' << argv[i] << '"' << std::endl; + } +} +``` + +```rust,noplayground +// src/main.rs + +use std::env; +use std::ffi::CString; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStrExt; +use std::ptr; + +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("example/include/args.h"); + + unsafe fn parseArgs(argc: i32, argv: *mut *mut c_char); + } +} + +fn main() { + // Convert from OsString to nul-terminated CString, truncating each argument + // at the first inner nul byte if present. + let args: Vec = env::args_os() + .map(|os_str| { + let bytes = os_str.as_bytes(); + CString::new(bytes).unwrap_or_else(|nul_error| { + let nul_position = nul_error.nul_position(); + let mut bytes = nul_error.into_vec(); + bytes.truncate(nul_position); + CString::new(bytes).unwrap() + }) + }) + .collect(); + + // Convert from Vec of owned strings to Vec<*mut c_char> of + // borrowed string pointers. + // + // Once extern type stabilizes (https://github.com/rust-lang/rust/issues/43467) + // and https://internals.rust-lang.org/t/pre-rfc-make-cstr-a-thin-pointer/6258 + // is implemented, and CStr pointers become thin, we can sidestep this step + // by accumulating the args as Vec> up front, then simply casting + // from *mut [Box] to *mut [*mut CStr] to *mut *mut c_char. + let argc = args.len(); + let mut argv: Vec<*mut c_char> = Vec::with_capacity(argc + 1); + for arg in &args { + argv.push(arg.as_ptr() as *mut c_char); + } + argv.push(ptr::null_mut()); // Nul terminator. + + unsafe { + ffi::parseArgs(argc as i32, argv.as_mut_ptr()); + } + + // The CStrings go out of scope here. C function must not have held on to + // the pointers beyond this point. +} +``` diff --git a/book/src/binding/result.md b/book/src/binding/result.md new file mode 100644 index 0000000..2a47531 --- /dev/null +++ b/book/src/binding/result.md @@ -0,0 +1,148 @@ +{{#title Result — Rust ♡ C++}} +# Result\ + +Result\ is allowed as the return type of an extern function in either +direction. Its behavior is to translate to/from C++ exceptions. If your codebase +does not use C++ exceptions, or prefers to represent fallibility using something +like outcome\, leaf::result\, StatusOr\, etc then you'll need to +handle the translation of those to Rust Result\ using your own shims for +now. Better support for this is planned. + +If an exception is thrown from an `extern "C++"` function that is *not* declared +by the CXX bridge to return Result, the program calls C++'s `std::terminate`. +The behavior is equivalent to the same exception being thrown through a +`noexcept` C++ function. + +If a panic occurs in *any* `extern "Rust"` function, regardless of whether it is +declared by the CXX bridge to return Result, a message is logged and the program +calls Rust's `std::process::abort`. + +## Returning Result from Rust to C++ + +An `extern "Rust"` function returning a Result turns into a `throw` in C++ if +the Rust side produces an error. + +Note that the return type written inside of cxx::bridge must be written without +a second type parameter. Only the Ok type is specified for the purpose of the +FFI. The Rust *implementation* (outside of the bridge module) may pick any error +type as long as it has a std::fmt::Display impl. + +```rust,noplayground +# use std::io; +# +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn fallible1(depth: usize) -> Result; + fn fallible2() -> Result<()>; + } +} + +fn fallible1(depth: usize) -> anyhow::Result { + if depth == 0 { + return Err(anyhow::Error::msg("fallible1 requires depth > 0")); + } + ... +} + +fn fallible2() -> Result<(), io::Error> { + ... + Ok(()) +} +``` + +The exception that gets thrown by CXX on the C++ side is always of type +`rust::Error` and has the following C++ public API. The `what()` member function +gives the error message according to the Rust error's std::fmt::Display impl. + +```cpp,hidelines=... +// rust/cxx.h +... +...namespace rust { + +class Error final : public std::exception { +public: + Error(const Error &); + Error(Error &&) noexcept; + ~Error() noexcept; + + Error &operator=(const Error &) &; + Error &operator=(Error &&) & noexcept; + + const char *what() const noexcept override; +}; +... +...} // namespace rust +``` + +## Returning Result from C++ to Rust + +An `extern "C++"` function returning a Result turns into a `catch` in C++ that +converts the exception into an Err for Rust. + +Note that the return type written inside of cxx::bridge must be written without +a second type parameter. Only the Ok type is specified for the purpose of the +FFI. The resulting error type created by CXX when an `extern "C++"` function +throws will always be of type **[`cxx::Exception`]**. + +[`cxx::Exception`]: https://docs.rs/cxx/*/cxx/struct.Exception.html + +```rust,noplayground +# use std::process; +# +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/example.h"); + fn fallible1(depth: usize) -> Result; + fn fallible2() -> Result<()>; + } +} + +fn main() { + if let Err(err) = ffi::fallible1(99) { + eprintln!("Error: {}", err); + process::exit(1); + } +} +``` + +The specific set of caught exceptions and the conversion to error message are +both customizable. The way you do this is by defining a template function +`rust::behavior::trycatch` with a suitable signature inside any one of the +headers `include!`'d by your cxx::bridge. + +The template signature is required to be: + +```cpp +namespace rust { +namespace behavior { + +template +static void trycatch(Try &&func, Fail &&fail) noexcept; + +} // namespace behavior +} // namespace rust +``` + +The default `trycatch` used by CXX if you have not provided your own is the +following. You must follow the same pattern: invoke `func` with no arguments, +catch whatever exception(s) you want, and invoke `fail` with the error message +you'd like for the Rust error to have. + +```cpp,hidelines=... +...#include +... +...namespace rust { +...namespace behavior { +... +template +static void trycatch(Try &&func, Fail &&fail) noexcept try { + func(); +} catch (const std::exception &e) { + fail(e.what()); +} +... +...} // namespace behavior +...} // namespace rust +``` diff --git a/book/src/binding/sharedptr.md b/book/src/binding/sharedptr.md new file mode 100644 index 0000000..a3b7070 --- /dev/null +++ b/book/src/binding/sharedptr.md @@ -0,0 +1,80 @@ +{{#title std::shared_ptr — Rust ♡ C++}} +# std::shared\_ptr\ + +The Rust binding of std::shared\_ptr\ is called **[`SharedPtr`]**. See +the link for documentation of the Rust API. + +[`SharedPtr`]: https://docs.rs/cxx/*/cxx/struct.SharedPtr.html + +### Restrictions: + +SharedPtr\ does not support T being an opaque Rust type. You should use a +Box\ (C++ [rust::Box\](box.md)) instead for transferring ownership of +opaque Rust types on the language boundary. + +## Example + +```rust,noplayground +// src/main.rs + +use std::ops::Deref; +use std::ptr; + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/example.h"); + + type Object; + + fn create_shared_ptr() -> SharedPtr; + } +} + +fn main() { + let ptr1 = ffi::create_shared_ptr(); + + { + // Create a second shared_ptr holding shared ownership of the same + // object. There is still only one Object but two SharedPtr. + // Both pointers point to the same object on the heap. + let ptr2 = ptr1.clone(); + assert!(ptr::eq(ptr1.deref(), ptr2.deref())); + + // ptr2 goes out of scope, but Object is not destroyed yet. + } + + println!("say goodbye to Object"); + + // ptr1 goes out of scope and Object is destroyed. +} +``` + +```cpp +// include/example.h + +#pragma once +#include + +class Object { +public: + Object(); + ~Object(); +}; + +std::shared_ptr create_shared_ptr(); +``` + +```cpp +// src/example.cc + +#include "example/include/example.h" +#include + +Object::Object() { std::cout << "construct Object" << std::endl; } +Object::~Object() { std::cout << "~Object" << std::endl; } + +std::shared_ptr create_shared_ptr() { + return std::make_shared(); +} +``` diff --git a/book/src/binding/slice.md b/book/src/binding/slice.md new file mode 100644 index 0000000..4054bce --- /dev/null +++ b/book/src/binding/slice.md @@ -0,0 +1,178 @@ +{{#title rust::Slice — Rust ♡ C++}} +# rust::Slice\, rust::Slice\ + +- Rust `&[T]` is written `rust::Slice` in C++ +- Rust `&mut [T]` is written `rust::Slice` in C++ + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...#include +...#include +... +...namespace rust { + +template +class Slice final { +public: + using value_type = T; + + Slice() noexcept; + Slice(const Slice &) noexcept; + Slice(T *, size_t count) noexcept; + + template + explicit Slice(C &c) : Slice(c.data(), c.size()); + + Slice &operator=(Slice &&) & noexcept; + Slice &operator=(const Slice &) & noexcept + requires std::is_const_v; + + T *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](size_t n) const noexcept; + T &at(size_t n) const; + T &front() const noexcept; + T &back() const noexcept; + + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; +}; +... +...template +...class Slice::iterator final { +...public: +...#if __cplusplus >= 202002L +... using iterator_category = std::contiguous_iterator_tag; +...#else +... using iterator_category = std::random_access_iterator_tag; +...#endif +... using value_type = T; +... using pointer = T *; +... using reference = T &; +... +... T &operator*() const noexcept; +... T *operator->() const noexcept; +... T &operator[](ptrdiff_t) const noexcept; +... +... iterator &operator++() noexcept; +... iterator operator++(int) noexcept; +... iterator &operator--() noexcept; +... iterator operator--(int) noexcept; +... +... iterator &operator+=(ptrdiff_t) noexcept; +... iterator &operator-=(ptrdiff_t) noexcept; +... iterator operator+(ptrdiff_t) const noexcept; +... iterator operator-(ptrdiff_t) const noexcept; +... ptrdiff_t operator-(const iterator &) const noexcept; +... +... bool operator==(const iterator &) const noexcept; +... bool operator!=(const iterator &) const noexcept; +... bool operator<(const iterator &) const noexcept; +... bool operator>(const iterator &) const noexcept; +... bool operator<=(const iterator &) const noexcept; +... bool operator>=(const iterator &) const noexcept; +...}; +... +...} // namespace rust +``` + +### Restrictions: + +T must not be an opaque Rust type or opaque C++ type. Support for opaque Rust +types in slices is coming. + +Allowed as function argument or return value. Not supported in shared structs. + +Only rust::Slice\ is copy-assignable, not rust::Slice\. (Both are +move-assignable.) You'll need to write std::move occasionally as a reminder that +accidentally exposing overlapping &mut \[T\] to Rust is UB. + +## Example + +This example is a C++ program that constructs a slice containing JSON data (by +reading from stdin, but it could be from anywhere), then calls into Rust to +pretty-print that JSON data into a std::string via the [serde_json] and +[serde_transcode] crates. + +[serde_json]: https://github.com/serde-rs/json +[serde_transcode]: https://github.com/sfackler/serde-transcode + +```rust,noplayground +// src/main.rs + +#![no_main] // main defined in C++ by main.cc + +use cxx::CxxString; +use std::io::{self, Write}; +use std::pin::Pin; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn prettify_json(input: &[u8], output: Pin<&mut CxxString>) -> Result<()>; + } +} + +struct WriteToCxxString<'a>(Pin<&'a mut CxxString>); + +impl<'a> Write for WriteToCxxString<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.as_mut().push_bytes(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn prettify_json(input: &[u8], output: Pin<&mut CxxString>) -> serde_json::Result<()> { + let writer = WriteToCxxString(output); + let mut deserializer = serde_json::Deserializer::from_slice(input); + let mut serializer = serde_json::Serializer::pretty(writer); + serde_transcode::transcode(&mut deserializer, &mut serializer) +} +``` + +```cpp +// src/main.cc + +#include "example/src/main.rs.h" +#include +#include +#include +#include + +int main() { + // Read json from stdin. + std::istreambuf_iterator begin{std::cin}, end; + std::vector input{begin, end}; + rust::Slice slice{input.data(), input.size()}; + + // Prettify using serde_json and serde_transcode. + std::string output; + prettify_json(slice, output); + + // Write to stdout. + std::cout << output << std::endl; +} +``` + +Testing the example: + +```console +$ echo '{"fearless":"concurrency"}' | cargo run + Finished dev [unoptimized + debuginfo] target(s) in 0.02s + Running `target/debug/example` +{ + "fearless": "concurrency" +} +``` diff --git a/book/src/binding/str.md b/book/src/binding/str.md new file mode 100644 index 0000000..214d12d --- /dev/null +++ b/book/src/binding/str.md @@ -0,0 +1,121 @@ +{{#title rust::Str — Rust ♡ C++}} +# rust::Str + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...#include +...#include +... +...namespace rust { + +class Str final { +public: + Str() noexcept; + Str(const Str &) noexcept; + Str(const String &) noexcept; + + // Throws std::invalid_argument if not utf-8. + Str(const std::string &); + Str(const char *); + Str(const char *, size_t); + + Str &operator=(const Str &) & noexcept; + + explicit operator std::string() const; +#if __cplusplus >= 201703L + explicit operator std::string_view() const; +#endif + + // Note: no null terminator. + const char *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + bool empty() const noexcept; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; +}; + +std::ostream &operator<<(std::ostream &, const Str &); +... +...} // namespace rust +``` + +### Notes: + +**Be aware that rust::Str behaves like &str i.e. it is a borrow!** C++ +needs to be mindful of the lifetimes at play. + +Just to reiterate: &str is rust::Str. Do not try to write &str as `const +rust::Str &`. A language-level C++ reference is not able to capture the fat +pointer nature of &str. + +### Restrictions: + +Allowed as function argument or return value. Not supported in shared structs +yet. `&mut str` is not supported yet, but is also extremely obscure so this is +fine. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn r(greeting: &str); + } + + unsafe extern "C++" { + include!("example/include/greeting.h"); + fn c(greeting: &str); + } +} + +fn r(greeting: &str) { + println!("{}", greeting); +} + +fn main() { + ffi::c("hello from Rust"); +} +``` + +```cpp +// include/greeting.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void c(rust::Str greeting); +``` + +```cpp +// src/greeting.cc + +#include "example/include/greeting.h" +#include + +void c(rust::Str greeting) { + std::cout << greeting << std::endl; + r("hello from C++"); +} +``` diff --git a/book/src/binding/string.md b/book/src/binding/string.md new file mode 100644 index 0000000..7875685 --- /dev/null +++ b/book/src/binding/string.md @@ -0,0 +1,134 @@ +{{#title rust::String — Rust ♡ C++}} +# rust::String + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...#include +...#include +... +...namespace rust { + +class String final { +public: + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + // Throws std::invalid_argument if not UTF-8. + String(const std::string &); + String(const char *); + String(const char *, size_t); + String(const char8_t *); + String(const char8_t *, size_t); + + // Replaces invalid UTF-8 data with the replacement character (U+FFFD). + static String lossy(const std::string &) noexcept; + static String lossy(const char *) noexcept; + static String lossy(const char *, size_t) noexcept; + + // Throws std::invalid_argument if not UTF-16. + String(const char16_t *); + String(const char16_t *, size_t); + + // Replaces invalid UTF-16 data with the replacement character (U+FFFD). + static String lossy(const char16_t *) noexcept; + static String lossy(const char16_t *, size_t) noexcept; + + String &operator=(const String &) & noexcept; + String &operator=(String &&) & noexcept; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + bool empty() const noexcept; + + const char *c_str() noexcept; + + size_t capacity() const noexcept; + void reserve(size_t new_cap) noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; +}; + +std::ostream &operator<<(std::ostream &, const String &); +... +...} // namespace rust +``` + +### Restrictions: + +None. Strings may be used as function arguments and function return values, by +value or by reference, as well as fields of shared structs. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct ConcatRequest { + fst: String, + snd: String, + } + + unsafe extern "C++" { + include!("example/include/concat.h"); + fn concat(r: ConcatRequest) -> String; + } +} + +fn main() { + let concatenated = ffi::concat(ffi::ConcatRequest { + fst: "fearless".to_owned(), + snd: "concurrency".to_owned(), + }); + println!("concatenated: {:?}", concatenated); +} +``` + +```cpp +// include/concat.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +rust::String concat(ConcatRequest r); +``` + +```cpp +// src/concat.cc + +#include "example/include/concat.h" + +rust::String concat(ConcatRequest r) { + // The full suite of operator overloads hasn't been added + // yet on rust::String, but we can get it done like this: + return std::string(r.fst) + std::string(r.snd); +} +``` diff --git a/book/src/binding/uniqueptr.md b/book/src/binding/uniqueptr.md new file mode 100644 index 0000000..eefbc34 --- /dev/null +++ b/book/src/binding/uniqueptr.md @@ -0,0 +1,63 @@ +{{#title std::unique_ptr — Rust ♡ C++}} +# std::unique\_ptr\ + +The Rust binding of std::unique\_ptr\ is called **[`UniquePtr`]**. See +the link for documentation of the Rust API. + +[`UniquePtr`]: https://docs.rs/cxx/*/cxx/struct.UniquePtr.html + +### Restrictions: + +Only `std::unique_ptr>` is currently supported. Custom +deleters may be supported in the future. + +UniquePtr\ does not support T being an opaque Rust type. You should use a +Box\ (C++ [rust::Box\](box.md)) instead for transferring ownership of +opaque Rust types on the language boundary. + +## Example + +UniquePtr is commonly useful for returning opaque C++ objects to Rust. This use +case was featured in the [*blobstore tutorial*](../tutorial.md). + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr; + // ... + } +} + +fn main() { + let client = ffi::new_blobstore_client(); + // ... +} +``` + +```cpp +// include/blobstore.h + +#pragma once +#include + +class BlobstoreClient; + +std::unique_ptr new_blobstore_client(); +``` + +```cpp +// src/blobstore.cc + +#include "example/include/blobstore.h" + +std::unique_ptr new_blobstore_client() { + return std::make_unique(); +} +``` diff --git a/book/src/binding/vec.md b/book/src/binding/vec.md new file mode 100644 index 0000000..3e883a2 --- /dev/null +++ b/book/src/binding/vec.md @@ -0,0 +1,200 @@ +{{#title rust::Vec — Rust ♡ C++}} +# rust::Vec\ + +### Public API: + +```cpp,hidelines=... +// rust/cxx.h +... +...#include +...#include +...#include +... +...namespace rust { + +template +class Vec final { +public: + using value_type = T; + + Vec() noexcept; + Vec(std::initializer_list); + Vec(const Vec &); + Vec(Vec &&) noexcept; + ~Vec() noexcept; + + Vec &operator=(Vec &&) & noexcept; + Vec &operator=(const Vec &) &; + + size_t size() const noexcept; + bool empty() const noexcept; + const T *data() const noexcept; + T *data() noexcept; + size_t capacity() const noexcept; + + const T &operator[](size_t n) const noexcept; + const T &at(size_t n) const; + const T &front() const; + const T &back() const; + + T &operator[](size_t n) noexcept; + T &at(size_t n); + T &front(); + T &back(); + + void reserve(size_t new_cap); + void push_back(const T &value); + void push_back(T &&value); + template + void emplace_back(Args &&...args); + void truncate(size_t len); + void clear(); + + class iterator; + iterator begin() noexcept; + iterator end() noexcept; + + class const_iterator; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + void swap(Vec &) noexcept; +}; +... +...template +...class Vec::iterator final { +...public: +...#if __cplusplus >= 202002L +... using iterator_category = std::contiguous_iterator_tag; +...#else +... using iterator_category = std::random_access_iterator_tag; +...#endif +... using value_type = T; +... using pointer = T *; +... using reference = T &; +... +... T &operator*() const noexcept; +... T *operator->() const noexcept; +... T &operator[](ptrdiff_t) const noexcept; +... +... iterator &operator++() noexcept; +... iterator operator++(int) noexcept; +... iterator &operator--() noexcept; +... iterator operator--(int) noexcept; +... +... iterator &operator+=(ptrdiff_t) noexcept; +... iterator &operator-=(ptrdiff_t) noexcept; +... iterator operator+(ptrdiff_t) const noexcept; +... iterator operator-(ptrdiff_t) const noexcept; +... ptrdiff_t operator-(const iterator &) const noexcept; +... +... bool operator==(const iterator &) const noexcept; +... bool operator!=(const iterator &) const noexcept; +... bool operator<(const iterator &) const noexcept; +... bool operator<=(const iterator &) const noexcept; +... bool operator>(const iterator &) const noexcept; +... bool operator>=(const iterator &) const noexcept; +...}; +... +...template +...class Vec::const_iterator final { +...public: +...#if __cplusplus >= 202002L +... using iterator_category = std::contiguous_iterator_tag; +...#else +... using iterator_category = std::random_access_iterator_tag; +...#endif +... using value_type = const T; +... using pointer = const T *; +... using reference = const T &; +... +... const T &operator*() const noexcept; +... const T *operator->() const noexcept; +... const T &operator[](ptrdiff_t) const noexcept; +... +... const_iterator &operator++() noexcept; +... const_iterator operator++(int) noexcept; +... const_iterator &operator--() noexcept; +... const_iterator operator--(int) noexcept; +... +... const_iterator &operator+=(ptrdiff_t) noexcept; +... const_iterator &operator-=(ptrdiff_t) noexcept; +... const_iterator operator+(ptrdiff_t) const noexcept; +... const_iterator operator-(ptrdiff_t) const noexcept; +... ptrdiff_t operator-(const const_iterator &) const noexcept; +... +... bool operator==(const const_iterator &) const noexcept; +... bool operator!=(const const_iterator &) const noexcept; +... bool operator<(const const_iterator &) const noexcept; +... bool operator<=(const const_iterator &) const noexcept; +... bool operator>(const const_iterator &) const noexcept; +... bool operator>=(const const_iterator &) const noexcept; +...}; +... +...} // namespace rust +``` + +### Restrictions: + +Vec\ does not support T being an opaque C++ type. You should use +CxxVector\ (C++ std::vector\) instead for collections of opaque C++ +types on the language boundary. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct Shared { + v: u32, + } + + unsafe extern "C++" { + include!("example/include/example.h"); + + fn f(elements: Vec); + } +} + +fn main() { + let shared = |v| ffi::Shared { v }; + let elements = vec![shared(3), shared(2), shared(1)]; + ffi::f(elements); +} +``` + +```cpp +// include/example.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void f(rust::Vec elements); +``` + +```cpp +// src/example.cc + +#include "example/include/example.h" +#include +#include +#include +#include +#include + +void f(rust::Vec v) { + for (auto shared : v) { + std::cout << shared.v << std::endl; + } + + // Copy the elements to a C++ std::vector using STL algorithm. + std::vector stdv; + std::copy(v.begin(), v.end(), std::back_inserter(stdv)); + assert(v.size() == stdv.size()); +} +``` diff --git a/book/src/bindings.md b/book/src/bindings.md new file mode 100644 index 0000000..bcb51d8 --- /dev/null +++ b/book/src/bindings.md @@ -0,0 +1,56 @@ +{{#title Built-in bindings — Rust ♡ C++}} +# Built-in bindings reference + +In addition to all the primitive types (i32 <=> int32_t), the following +common types may be used in the fields of shared structs and the arguments and +returns of extern functions. + +
    + + + + + + + + + + + + + + + + + +
    name in Rustname in C++restrictions
    Stringrust::String
    &strrust::Str
    &[T]rust::Slice<const T>cannot hold opaque C++ type
    &mut [T]rust::Slice<T>cannot hold opaque C++ type
    CxxStringstd::stringcannot be passed by value
    Box<T>rust::Box<T>cannot hold opaque C++ type
    UniquePtr<T>std::unique_ptr<T>cannot hold opaque Rust type
    SharedPtr<T>std::shared_ptr<T>cannot hold opaque Rust type
    [T; N]std::array<T, N>cannot hold opaque C++ type
    Vec<T>rust::Vec<T>cannot hold opaque C++ type
    CxxVector<T>std::vector<T>cannot be passed by value, cannot hold opaque Rust type
    *mut T, *const TT*, const T*fn with a raw pointer argument must be declared unsafe to call
    fn(T, U) -> Vrust::Fn<V(T, U)>only passing from Rust to C++ is implemented so far
    Result<T>throw/catchallowed as return type only
    + +
    + +The C++ API of the `rust` namespace is defined by the *include/cxx.h* file in +the CXX GitHub repo. You will need to include this header in your C++ code when +working with those types. **When using Cargo and the cxx-build crate, the header +is made available to you at `#include "rust/cxx.h"`.** + +The `rust` namespace additionally provides lowercase type aliases of all the +types mentioned in the table, for use in codebases preferring that style. For +example `rust::String`, `rust::Vec` may alternatively be written `rust::string`, +`rust::vec` etc. + +## Pending bindings + +The following types are intended to be supported "soon" but are just not +implemented yet. I don't expect any of these to be hard to make work but it's a +matter of designing a nice API for each in its non-native language. + +
    + + + + + + + + + +
    name in Rustname in C++
    BTreeMap<K, V>tbd
    HashMap<K, V>tbd
    Arc<T>tbd
    Option<T>tbd
    tbdstd::map<K, V>
    tbdstd::unordered_map<K, V>
    diff --git a/book/src/build/bazel.md b/book/src/build/bazel.md new file mode 100644 index 0000000..698bded --- /dev/null +++ b/book/src/build/bazel.md @@ -0,0 +1,109 @@ +{{#title Bazel, Buck2 — Rust ♡ C++}} +## Bazel, Buck2, potentially other similar environments + +Starlark-based build systems with the ability to compile a code generator and +invoke it as a `genrule` will run CXX's C++ code generator via its `cxxbridge` +command line interface. + +The tool is packaged as the `cxxbridge-cmd` crate on crates.io or can be built +from the *gen/cmd/* directory of the CXX GitHub repo. + +```console +$ cargo install cxxbridge-cmd + +$ cxxbridge src/bridge.rs --header > path/to/bridge.rs.h +$ cxxbridge src/bridge.rs > path/to/bridge.rs.cc +``` + +The CXX repo maintains working [Bazel] `BUILD.bazel` and [Buck2] `BUCK` targets +for the complete blobstore tutorial (chapter 3) for your reference, tested in +CI. These aren't meant to be directly what you use in your codebase, but serve +as an illustration of one possible working pattern. + +[Bazel]: https://bazel.build +[Buck2]: https://buck2.build + +```python +# tools/bazel/rust_cxx_bridge.bzl + +load("@bazel_skylib//rules:run_binary.bzl", "run_binary") +load("@rules_cc//cc:defs.bzl", "cc_library") + +def rust_cxx_bridge(name, src, deps = []): + native.alias( + name = "%s/header" % name, + actual = src + ".h", + ) + + native.alias( + name = "%s/source" % name, + actual = src + ".cc", + ) + + run_binary( + name = "%s/generated" % name, + srcs = [src], + outs = [ + src + ".h", + src + ".cc", + ], + args = [ + "$(location %s)" % src, + "-o", + "$(location %s.h)" % src, + "-o", + "$(location %s.cc)" % src, + ], + tool = "//:codegen", + ) + + cc_library( + name = name, + srcs = [src + ".cc"], + deps = deps + [":%s/include" % name], + ) + + cc_library( + name = "%s/include" % name, + hdrs = [src + ".h"], + ) +``` + +```python +# demo/BUILD.bazel + +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//tools/bazel:rust_cxx_bridge.bzl", "rust_cxx_bridge") + +rust_binary( + name = "demo", + srcs = glob(["src/**/*.rs"]), + deps = [ + ":blobstore-sys", + ":bridge", + "//:cxx", + ], +) + +rust_cxx_bridge( + name = "bridge", + src = "src/main.rs", + deps = [":blobstore-include"], +) + +cc_library( + name = "blobstore-sys", + srcs = ["src/blobstore.cc"], + deps = [ + ":blobstore-include", + ":bridge/include", + ], +) + +cc_library( + name = "blobstore-include", + hdrs = ["include/blobstore.h"], + deps = ["//:core"], +) +``` diff --git a/book/src/build/cargo.md b/book/src/build/cargo.md new file mode 100644 index 0000000..6e9af80 --- /dev/null +++ b/book/src/build/cargo.md @@ -0,0 +1,306 @@ +{{#title Cargo-based setup — Rust ♡ C++}} +# Cargo-based builds + +As one aspect of delivering a good Rust–C++ interop experience, CXX turns +Cargo into a quite usable build system for C++ projects published as a +collection of crates.io packages, including a consistent and frictionless +experience `#include`-ing C++ headers across dependencies. + +## Canonical setup + +CXX's integration with Cargo is handled through the [cxx-build] crate. + +[cxx-build]: https://docs.rs/cxx-build + +```toml,hidelines=... +# Cargo.toml +...[package] +...name = "..." +...version = "..." +...edition = "2021" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +The canonical build script is as follows. The indicated line returns a +[`cc::Build`] instance (from the usual widely used `cc` crate) on which you can +set up any additional source files and compiler flags as normal. + +[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html + +```rust,noplayground +// build.rs + +fn main() { + cxx_build::bridge("src/main.rs") // returns a cc::Build + .file("src/demo.cc") + .std("c++11") + .compile("cxxbridge-demo"); + + println!("cargo:rerun-if-changed=src/main.rs"); + println!("cargo:rerun-if-changed=src/demo.cc"); + println!("cargo:rerun-if-changed=include/demo.h"); +} +``` + +The `rerun-if-changed` lines are optional but make it so that Cargo does not +spend time recompiling your C++ code when only non-C++ code has changed since +the previous Cargo build. By default without any `rerun-if-changed`, Cargo will +re-execute the build script after *any* file changed in the project. + +If stuck, try comparing what you have against the *demo/* directory of the CXX +GitHub repo, which maintains a working Cargo-based setup for the blobstore +tutorial (chapter 3). + +## Header include paths + +With cxx-build, by default your include paths always start with the crate name. +This applies to both `#include` within your C++ code, and `include!` in the +`extern "C++"` section of your Rust cxx::bridge. + +Your crate name is determined by the `name` entry in Cargo.toml. + +For example if your crate is named `yourcratename` and contains a C++ header +file `path/to/header.h` relative to Cargo.toml, that file will be includable as: + +```cpp +#include "yourcratename/path/to/header.h" +``` + +A crate can choose a prefix for its headers that is different from the crate +name by modifying **[`CFG.include_prefix`][CFG]** from build.rs: + +[CFG]: https://docs.rs/cxx-build/*/cxx_build/static.CFG.html + +```rust,noplayground +// build.rs + +use cxx_build::CFG; + +fn main() { + CFG.include_prefix = "my/project"; + + cxx_build::bridge(...)... +} +``` + +Subsequently the header located at `path/to/header.h` would now be includable +as: + +```cpp +#include "my/project/path/to/header.h" +``` + +The empty string `""` is a valid include prefix and will make it possible to +have `#include "path/to/header.h"`. However, if your crate is a library, be +considerate of possible name collisions that may occur in downstream crates. If +using an empty include prefix, you'll want to make sure your headers' local path +within the crate is sufficiently namespaced or unique. + +## Including generated code + +If your `#[cxx::bridge]` module contains an `extern "Rust"` block i.e. types or +functions exposed from Rust to C++, or any shared data structures, the +CXX-generated C++ header declaring those things is available using a `.rs.h` +extension on the Rust source file's name. + +```cpp +// the header generated from path/to/lib.rs +#include "yourcratename/path/to/lib.rs.h" +``` + +For giggles, it's also available using just a plain `.rs` extension as if you +were including the Rust file directly. Use whichever you find more palatable. + +```cpp +#include "yourcratename/path/to/lib.rs" +``` + +## Including headers from dependencies + +You get to include headers from your dependencies, both handwritten ones +contained as `.h` files in their Cargo package, as well as CXX-generated ones. + +It works the same as an include of a local header: use the crate name (or their +include\_prefix if their crate changed it) followed by the relative path of the +header within the crate. + +```cpp +#include "dependencycratename/path/to/their/header.h` +``` + +Note that cross-crate imports are only made available between **direct +dependencies**. You must directly depend on the other crate in order to #include +its headers; a transitive dependency is not sufficient. + +Additionally, headers from a direct dependency are only importable if the +dependency's Cargo.toml manifest contains a `links` key. If not, its headers +will not be importable from outside of the same crate. See *[the `links` +manifest key][links]* in the Cargo reference. + +[links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key + +


    + +# Advanced features + +The following CFG settings are only relevant to you if you are writing a library +that needs to support downstream crates `#include`-ing its C++ public headers. + +## Publicly exporting header directories + +**[`CFG.exported_header_dirs`][CFG]** (vector of absolute paths) defines a set +of additional directories from which the current crate, directly dependent +crates, and further crates to which this crate's headers are exported (more +below) will be able to `#include` headers. + +Adding a directory to `exported_header_dirs` is similar to adding it to the +current build via the `cc` crate's [`Build::include`], but *also* makes the +directory available to downstream crates that want to `#include` one of the +headers from your crate. If the dir were added only using `Build::include`, the +downstream crate including your header would need to manually add the same +directory to their own build as well. + +[`Build::include`]: https://docs.rs/cc/1/cc/struct.Build.html#method.include + +When using `exported_header_dirs`, your crate must also set a `links` key for +itself in Cargo.toml. See [*the `links` manifest key*][links]. The reason is +that Cargo imposes no ordering on the execution of build scripts without a +`links` key, which means the downstream crate's build script might otherwise +execute before yours decides what to put into `exported_header_dirs`. + +### Example + +One of your crate's headers wants to include a system library, such as `#include +"Python.h"`. + +```rust,noplayground +// build.rs + +use cxx_build::CFG; +use std::path::PathBuf; + +fn main() { + let python3 = pkg_config::probe_library("python3").unwrap(); + let python_include_paths = python3.include_paths.iter().map(PathBuf::as_path); + CFG.exported_header_dirs.extend(python_include_paths); + + cxx_build::bridge("src/bridge.rs").compile("demo"); +} +``` + +### Example + +Your crate wants to rearrange the headers that it exports vs how they're laid +out locally inside the crate's source directory. + +Suppose the crate as published contains a file at `./include/myheader.h` but +wants it available to downstream crates as `#include "foo/v1/public.h"`. + +```rust,noplayground +// build.rs + +use cxx_build::CFG; +use std::path::Path; +use std::{env, fs}; + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let headers = Path::new(&out_dir).join("headers"); + CFG.exported_header_dirs.push(&headers); + + // We contain `include/myheader.h` locally, but + // downstream will use `#include "foo/v1/public.h"` + let foo = headers.join("foo").join("v1"); + fs::create_dir_all(&foo).unwrap(); + fs::copy("include/myheader.h", foo.join("public.h")).unwrap(); + + cxx_build::bridge("src/bridge.rs").compile("demo"); +} +``` + +## Publicly exporting dependencies + +**[`CFG.exported_header_prefixes`][CFG]** (vector of strings) each refer to the +`include_prefix` of one of your direct dependencies, or a prefix thereof. They +describe which of your dependencies participate in your crate's C++ public API, +as opposed to private use by your crate's implementation. + +As a general rule, if one of your headers `#include`s something from one of your +dependencies, you need to put that dependency's `include_prefix` into +`CFG.exported_header_prefixes` (*or* their `links` key into +`CFG.exported_header_links`; see below). On the other hand if only your C++ +implementation files and *not* your headers are importing from the dependency, +you do not export that dependency. + +The significance of exported headers is that if downstream code (crate **𝒜**) +contains an `#include` of a header from your crate (**ℬ**) and your header +contains an `#include` of something from your dependency (**𝒞**), the exported +dependency **𝒞** becomes available during the downstream crate **𝒜**'s build. +Otherwise the downstream crate **𝒜** doesn't know about **𝒞** and wouldn't be +able to find what header your header is referring to, and would fail to build. + +When using `exported_header_prefixes`, your crate must also set a `links` key +for itself in Cargo.toml. + +### Example + +Suppose you have a crate with 5 direct dependencies and the `include_prefix` for +each one are: + +- "crate0" +- "group/api/crate1" +- "group/api/crate2" +- "group/api/contrib/crate3" +- "detail/crate4" + +Your header involves types from the first four so we re-export those as part of +your public API, while crate4 is only used internally by your cc file not your +header, so we do not export: + +```rust,noplayground +// build.rs + +use cxx_build::CFG; + +fn main() { + CFG.exported_header_prefixes = vec!["crate0", "group/api"]; + + cxx_build::bridge("src/bridge.rs") + .file("src/impl.cc") + .compile("demo"); +} +``` + +
    + +For more fine grained control, there is **[`CFG.exported_header_links`][CFG]** +(vector of strings) which each refer to the `links` attribute ([*the `links` +manifest key*][links]) of one of your crate's direct dependencies. + +This achieves an equivalent result to `CFG.exported_header_prefixes` by +re-exporting a C++ dependency as part of your crate's public API, except with +finer control for cases when multiple crates might be sharing the same +`include_prefix` and you'd like to export some but not others. Links attributes +are guaranteed to be unique identifiers by Cargo. + +When using `exported_header_links`, your crate must also set a `links` key for +itself in Cargo.toml. + +### Example + +```rust,noplayground +// build.rs + +use cxx_build::CFG; + +fn main() { + CFG.exported_header_links.push("git2"); + + cxx_build::bridge("src/bridge.rs").compile("demo"); +} +``` diff --git a/book/src/build/cmake.md b/book/src/build/cmake.md new file mode 100644 index 0000000..478552e --- /dev/null +++ b/book/src/build/cmake.md @@ -0,0 +1,47 @@ +{{#title CMake — Rust ♡ C++}} +# CMake + +There is not an officially endorsed CMake setup for CXX, but a few developers +have shared one that they got working. You can try one of these as a starting +point. If you feel that you have arrived at a CMake setup that is superior to +what is available in these links, feel free to make a PR adding it to this list. + +
    + +--- + +- **** + + - Supports cross-language link time optimization (LTO) + +--- + +- **** + + - Includes a cbindgen component + - Tested on Windows 10 with MSVC, and on Linux + +--- + +- **** + + - Alias target that can be linked into a C++ project + - Tested on Windows 10 with GNU target, and on Linux + +--- + +- **** + + - Improved rusty_cmake CMake file to use modern C++ + - Rich examples of using different primitive types and Rust's Result return to C++ + - MacOS and Linux only + +--- + +- **** + + - Same blobstore example as the official demo, but inverted languages + - Minimal CMake configuration + - Tested on Linux, macOS, and Windows + +--- diff --git a/book/src/build/other.md b/book/src/build/other.md new file mode 100644 index 0000000..c0c6e91 --- /dev/null +++ b/book/src/build/other.md @@ -0,0 +1,87 @@ +{{#title Other build systems — Rust ♡ C++}} +# Some other build system + +You will need to achieve at least these three things: + +- Produce the CXX-generated C++ bindings code. +- Compile the generated C++ code. +- Link the resulting objects together with your other C++ and Rust objects. + +*Not all build systems are created equal. If you're hoping to use a build system +from the '90s, especially if you're hoping to overlaying the limitations of 2 or +more build systems (like automake+cargo) and expect to solve them +simultaneously, then be mindful that your expectations are set accordingly and +seek sympathy from those who have imposed the same approach on themselves.* + +### Producing the generated code + +CXX's Rust code generation automatically happens when the `#[cxx::bridge]` +procedural macro is expanded during the normal Rust compilation process, so no +special build steps are required there. + +But the C++ side of the bindings needs to be generated. Your options are: + +- Use the `cxxbridge` command, which is a standalone command line interface to + the CXX C++ code generator. Wire up your build system to compile and invoke + this tool. + + ```console + $ cxxbridge src/bridge.rs --header > path/to/bridge.rs.h + $ cxxbridge src/bridge.rs > path/to/bridge.rs.cc + ``` + + It's packaged as the `cxxbridge-cmd` crate on crates.io or can be built from + the *gen/cmd/* directory of the CXX GitHub repo. + +- Or, build your own code generator frontend on top of the [cxx-gen] crate. This + is currently unofficial and unsupported. + +[cxx-gen]: https://docs.rs/cxx-gen + +### Compiling C++ + +However you like. We can provide no guidance. + +### Linking the C++ and Rust together + +When linking a binary which contains mixed Rust and C++ code, you will have to +choose between using the Rust toolchain (`rustc`) or the C++ toolchain which you +may already have extensively tuned. + +The generated C++ code and the Rust code generated by the procedural macro both +depend on each other. Simple examples may only require one or the other, but in +general your linking will need to handle both directions. For some linkers, such +as llvm-ld, this is not a problem at all. For others, such as GNU ld, flags like +`--start-lib`/`--end-lib` may help. + +Rust does not generate simple standalone `.o` files, so you can't just throw the +Rust-generated code into your existing C++ toolchain linker. Instead you need to +choose one of these options: + +* Use `rustc` as the final linker. Pass any non-Rust libraries using `-L + ` and `-l` rustc arguments, and/or `#[link]` directives in + your Rust code. If you need to link against C/C++ `.o` files you can use + `-Clink-arg=file.o`. + +* Use your C++ linker. In this case, you first need to use `rustc` and/or + `cargo` to generate a _single_ Rust `staticlib` target and pass that into your + foreign linker invocation. + + * If you need to link multiple Rust subsystems, you will need to generate a + _single_ `staticlib` perhaps using lots of `extern crate` statements to + include multiple Rust `rlib`s. Multiple Rust `staticlib` files are likely + to conflict. + +Passing Rust `rlib`s directly into your non-Rust linker is not supported (but +apparently sometimes works). + +See the [Rust reference's *Linkage*][linkage] page for some general information +here. + +[linkage]: https://doc.rust-lang.org/reference/linkage.html + +The following open rust-lang issues might hold more recent guidance or +inspiration: [rust-lang/rust#73632], [rust-lang/rust#73295]. + +[rust-lang/rust#73632]: https://github.com/rust-lang/rust/issues/73632 +[rust-lang/rust#73295]: https://github.com/rust-lang/rust/issues/73295 diff --git a/book/src/building.md b/book/src/building.md new file mode 100644 index 0000000..c75939e --- /dev/null +++ b/book/src/building.md @@ -0,0 +1,20 @@ +{{#title Multi-language build system options — Rust ♡ C++}} +# Multi-language build system options + +CXX is designed to be convenient to integrate into a variety of build systems. + +If you are working in a project that does not already have a preferred build +system for its C++ code *or* which will be relying heavily on open source +libraries from the Rust package registry, you're likely to have the easiest +experience with Cargo which is the build system commonly used by open source +Rust projects. Refer to the ***[Cargo](build/cargo.md)*** chapter about CXX's +Cargo support. + +Among build systems designed for first class multi-language support, Bazel is a +solid choice. Refer to the ***[Bazel](build/bazel.md)*** chapter. + +If your codebase is already invested in CMake, refer to the +***[CMake](build/cmake.md)*** chapter. + +If you have some other build system that you'd like to try to make work with +CXX, see [this page](build/other.md) for notes. diff --git a/book/src/concepts.md b/book/src/concepts.md new file mode 100644 index 0000000..75daedd --- /dev/null +++ b/book/src/concepts.md @@ -0,0 +1,85 @@ +{{#title Core concepts — Rust ♡ C++}} +# Core concepts + +This page is a brief overview of the major concepts of CXX, enough so that you +recognize the shape of things as you read the tutorial and following chapters. + +In CXX, the language of the FFI boundary involves 3 kinds of items: + +- **Shared structs** — data structures whose fields are made visible to + both languages. The definition written within cxx::bridge in Rust is usually + the single source of truth, though there are ways to do sharing based on a + bindgen-generated definition with C++ as source of truth. + +- **Opaque types** — their fields are secret from the other language. + These cannot be passed across the FFI by value but only behind an indirection, + such as a reference `&`, a Rust `Box`, or a C++ `unique_ptr`. Can be a type + alias for an arbitrarily complicated generic language-specific type depending + on your use case. + +- **Functions** — implemented in either language, callable from the other + language. + +```rust,noplayground,focuscomment +# #[cxx::bridge] +# mod ffi { + // Any shared structs, whose fields will be visible to both languages. +# struct BlobMetadata { +# size: usize, +# tags: Vec, +# } +# +# extern "Rust" { + // Zero or more opaque types which both languages can pass around + // but only Rust can see the fields. +# type MultiBuf; +# + // Functions implemented in Rust. +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { + // One or more headers with the matching C++ declarations for the + // enclosing extern "C++" block. Our code generators don't read it + // but it gets #include'd and used in static assertions to ensure + // our picture of the FFI boundary is accurate. +# include!("demo/include/blobstore.h"); +# + // Zero or more opaque types which both languages can pass around + // but only C++ can see the fields. +# type BlobstoreClient; +# + // Functions implemented in C++. +# fn new_blobstore_client() -> UniquePtr; +# fn put(&self, parts: &mut MultiBuf) -> u64; +# fn tag(&self, blobid: u64, tag: &str); +# fn metadata(&self, blobid: u64) -> BlobMetadata; +# } +# } +``` + +Within the `extern "Rust"` part of the CXX bridge we list the types and +functions for which Rust is the source of truth. These all implicitly refer to +the `super` module, the parent module of the CXX bridge. You can think of the +two items listed in the example above as being like `use super::MultiBuf` and +`use super::next_chunk` except re-exported to C++. The parent module will either +contain the definitions directly for simple things, or contain the relevant +`use` statements to bring them into scope from elsewhere. + +Within the `extern "C++"` part, we list types and functions for which C++ is the +source of truth, as well as the header(s) that declare those APIs. In the future +it's possible that this section could be generated bindgen-style from the +headers but for now we need the signatures written out; static assertions verify +that they are accurate. + +

    + +Be aware that the design of this library is intentionally restrictive and +opinionated! It isn't a goal to be flexible enough to handle an arbitrary +signature in either language. Instead this project is about carving out a highly +expressive set of functionality about which we can make powerful safety +guarantees today and extend over time. You may find that it takes some practice +to use CXX bridge effectively as it won't work in all the ways that you may be +used to. + +
    diff --git a/book/src/context.md b/book/src/context.md new file mode 100644 index 0000000..516ee91 --- /dev/null +++ b/book/src/context.md @@ -0,0 +1,118 @@ +{{#title Other Rust–C++ interop tools — Rust ♡ C++}} +# Context: other Rust–C++ interop tools + +When it comes to interacting with an idiomatic Rust API or idiomatic C++ API +from the other language, the generally applicable approaches outside of the CXX +crate are: + +- Build a C-compatible wrapper around the code (expressed using `extern "C"` + signatures, primitives, C-compatible structs, raw pointers). Translate that + manually to equivalent `extern "C"` declarations in the other language and + keep them in sync. Preferably, build a safe/idiomatic wrapper around the + translated `extern "C"` signatures for callers to use. + +- Build a C wrapper around the C++ code and use **[bindgen]** to translate that + programmatically to `extern "C"` Rust signatures. Preferably, build a + safe/idiomatic Rust wrapper on top. + +- Build a C-compatible Rust wrapper around the Rust code and use **[cbindgen]** + to translate that programmatically to an `extern "C"` C++ header. Preferably, + build an idiomatic C++ wrapper. + +**If the code you are binding is already *"effectively C"*, the above has you +covered.** You should use bindgen or cbindgen, or manually translated C +signatures if there aren't too many and they seldom change. + +[bindgen]: https://github.com/rust-lang/rust-bindgen +[cbindgen]: https://github.com/eqrion/cbindgen + +## C++ vs C + +Bindgen has some basic support for C++. It can reason about classes, member +functions, and the layout of templated types. However, everything it does +related to C++ is best-effort only. Bindgen starts from a point of wanting to +generate declarations for everything, so any C++ detail that it hasn't +implemented will cause a crash if you are lucky ([bindgen#388]) or more likely +silently emit an incompatible signature ([bindgen#380], [bindgen#607], +[bindgen#652], [bindgen#778], [bindgen#1194]) which will do arbitrary +memory-unsafe things at runtime whenever called. + +[bindgen#388]: https://github.com/rust-lang/rust-bindgen/issues/388 +[bindgen#380]: https://github.com/rust-lang/rust-bindgen/issues/380 +[bindgen#607]: https://github.com/rust-lang/rust-bindgen/issues/607 +[bindgen#652]: https://github.com/rust-lang/rust-bindgen/issues/652 +[bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778 +[bindgen#1194]: https://github.com/rust-lang/rust-bindgen/issues/1194 + +Thus using bindgen correctly requires not just juggling all your pointers +correctly at the language boundary, but also understanding ABI details and their +workarounds and reliably applying them. For example, the programmer will +discover that their program sometimes segfaults if they call a function that +returns std::unique\_ptr\ through bindgen. Why? Because unique\_ptr, despite +being "just a pointer", has a different ABI than a pointer or a C struct +containing a pointer ([bindgen#778]) and is not directly expressible in Rust. +Bindgen emitted something that *looks* reasonable and you will have a hell of a +time in gdb working out what went wrong. Eventually people learn to avoid +anything involving a non-trivial copy constructor, destructor, or inheritance, +and instead stick to raw pointers and primitives and trivial structs only +— in other words C. + +## Geometric intuition for why there is so much opportunity for improvement + +The CXX project attempts a different approach to C++ FFI. + +Imagine Rust and C and C++ as three vertices of a scalene triangle, with length +of the edges being related to similarity of the languages when it comes to +library design. + +The most similar pair (the shortest edge) is Rust–C++. These languages +have largely compatible concepts of things like ownership, vectors, strings, +fallibility, etc that translate clearly from signatures in either language to +signatures in the other language. + +When we make a binding for an idiomatic C++ API using bindgen, and we fall down +to raw pointers and primitives and trivial structs as described above, what we +are really doing is coding the two longest edges of the triangle: getting from +C++ down to C, and C back up to Rust. The Rust–C edge always involves a +great deal of `unsafe` code, and the C++–C edge similarly requires care +just for basic memory safety. Something as basic as "how do I pass ownership of +a string to the other language?" becomes a strap-yourself-in moment, +particularly for someone not already an expert in one or both sides. + +You should think of the `cxx` crate as being the midpoint of the Rust–C++ +edge. Rather than coding the two long edges, you will code half the short edge +in Rust and half the short edge in C++, in both cases with the library playing +to the strengths of the Rust type system *and* the C++ type system to help +assure correctness. + +If you've already been through the tutorial in the previous chapter, take a +moment to appreciate that the C++ side *really* looks like we are just writing +C++ and the Rust side *really* looks like we are just writing Rust. Anything you +could do wrong in Rust, and almost anything you could reasonably do wrong in +C++, will be caught by the compiler. This highlights that we are on the "short +edge of the triangle". + +But it all still boils down to the same things: it's still FFI from one piece of +native code to another, nothing is getting serialized or allocated or +runtime-checked in between. + +## Role of CXX + +The role of CXX is to capture the language boundary with more fidelity than what +`extern "C"` is able to represent. You can think of CXX as being a replacement +for `extern "C"` in a sense. + +From this perspective, CXX is a lower level tool than the bindgens. Just as +bindgen and cbindgen are built on top of `extern "C"`, it makes sense to think +about higher level tools built on top of CXX. Such a tool might consume a C++ +header and/or Rust module (and/or IDL like Thrift) and emit the corresponding +safe cxx::bridge language boundary, leveraging CXX's static analysis and +underlying implementation of that boundary. We are beginning to see this space +explored by the [autocxx] tool, though nothing yet ready for broad use in the +way that CXX on its own is. + +[autocxx]: https://github.com/google/autocxx + +But note in other ways CXX is higher level than the bindgens, with rich support +for common standard library types. CXX's types serve as an intuitive vocabulary +for designing a good boundary between components in different languages. diff --git a/book/src/cxx.png b/book/src/cxx.png new file mode 100644 index 0000000..07118aa Binary files /dev/null and b/book/src/cxx.png differ diff --git a/book/src/extern-c++.md b/book/src/extern-c++.md new file mode 100644 index 0000000..11ed7b5 --- /dev/null +++ b/book/src/extern-c++.md @@ -0,0 +1,352 @@ +{{#title extern "C++" — Rust ♡ C++}} +# extern "C++" + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("path/to/header.h"); + include!("path/to/another.h"); + + ... + } +} +``` + +The `extern "C++"` section of a CXX bridge declares C++ types and signatures to +be made available to Rust, and gives the paths of the header(s) which contain +the corresponding C++ declarations. + +A bridge module may contain zero or more extern "C++" blocks. + +## Opaque C++ types + +Type defined in C++ that are made available to Rust, but only behind an +indirection. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "C++" { + # include!("path/to/header.h"); + # + type MyType; + type MyOtherType; + } +# } +``` + +For example in the ***[Tutorial](tutorial.md)*** we saw `BlobstoreClient` +implemented as an opaque C++ type. The blobstore client was created in C++ and +returned to Rust by way of a UniquePtr. + +**Mutability:** Unlike extern Rust types and shared types, an extern C++ type is +not permitted to be passed by plain mutable reference `&mut MyType` across the +FFI bridge. For mutation support, the bridge is required to use `Pin<&mut +MyType>`. This is to safeguard against things like mem::swap-ing the contents of +two mutable references, given that Rust doesn't have information about the size +of the underlying object and couldn't invoke an appropriate C++ move constructor +anyway. + +**Thread safety:** Be aware that CXX does not assume anything about the thread +safety of your extern C++ types. In other words the `MyType` etc bindings which +CXX produces for you in Rust *do not* come with `Send` and `Sync` impls. If you +are sure that your C++ type satisfies the requirements of `Send` and/or `Sync` +and need to leverage that fact from Rust, you must provide your own unsafe +marker trait impls. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { +# extern "C++" { +# include!("path/to/header.h"); +# +# type MyType; +# } +# } +# +/// The C++ implementation of MyType is thread safe. +unsafe impl Send for ffi::MyType {} +unsafe impl Sync for ffi::MyType {} +``` + +Take care in doing this because thread safety in C++ can be extremely tricky to +assess if you are coming from a Rust background. For example the +`BlobstoreClient` type in the tutorial is *not thread safe* despite doing only +completely innocuous things in its implementation. Concurrent calls to the `tag` +member function trigger a data race on the `blobs` map. + +## Functions and member functions + +This largely follows the same principles as ***[extern +"Rust"](extern-rust.md)*** functions and methods. In particular, any signature +with a `self` parameter is interpreted as a C++ non-static member function and +exposed to Rust as a method. + +The programmer **does not** need to promise that the signatures they have typed +in are accurate; that would be unreasonable. CXX performs static assertions that +the signatures exactly correspond with what is declared in C++. Rather, the +programmer is only on the hook for things that C++'s static information is not +precise enough to capture, i.e. things that would only be represented at most by +comments in the C++ code unintelligible to a static assertion: namely whether +the C++ function is safe or unsafe to be called from Rust. + +**Safety:** the extern "C++" block is responsible for deciding whether to expose +each signature inside as safe-to-call or unsafe-to-call. If an extern block +contains at least one safe-to-call signature, it must be written as an `unsafe +extern` block, which serves as an item level unsafe block to indicate that an +unchecked safety claim is being made about the contents of the block. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + # include!("path/to/header.h"); + # + fn f(); // safe to call + } + + extern "C++" { + unsafe fn g(); // unsafe to call + } +} +``` + +## Lifetimes + +C++ types holding borrowed data may be described naturally in Rust by an extern +type with a generic lifetime parameter. For example in the case of the following +pair of types: + +```cpp +// header.h + +class Resource; + +class TypeContainingBorrow { + TypeContainingBorrow(const Resource &res) : res(res) {} + const Resource &res; +}; + +std::shared_ptr create(const Resource &res); +``` + +we'd want to expose this to Rust as: + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + # include!("path/to/header.h"); + # + type Resource; + type TypeContainingBorrow<'a>; + + fn create<'a>(res: &'a Resource) -> SharedPtr>; + + // or with lifetime elision: + fn create(res: &Resource) -> SharedPtr; + } +} +``` + +## Reusing existing binding types + +Extern C++ types support a syntax for declaring that a Rust binding of the +correct C++ type already exists outside of the current bridge module. This +avoids generating a fresh new binding which Rust's type system would consider +non-interchangeable with the first. + +```rust,noplayground +#[cxx::bridge(namespace = "path::to")] +mod ffi { + extern "C++" { + type MyType = crate::existing::MyType; + } + + extern "Rust" { + fn f(x: &MyType) -> usize; + } +} +``` + +In this case rather than producing a unique new Rust type `ffi::MyType` for the +Rust binding of C++'s `::path::to::MyType`, CXX will reuse the already existing +binding at `crate::existing::MyType` in expressing the signature of `f` and any +other uses of `MyType` within the bridge module. + +CXX safely validates that `crate::existing::MyType` is in fact a binding for the +right C++ type `::path::to::MyType` by generating a static assertion based on +`crate::existing::MyType`'s implementation of [`ExternType`], which is a trait +automatically implemented by CXX for bindings that it generates but can also be +manually implemented as described below. + +[`ExternType`]: https://docs.rs/cxx/*/cxx/trait.ExternType.html + +`ExternType` serves the following two related use cases. + +#### Safely unifying occurrences of an extern type across bridges + +In the following snippet, two #\[cxx::bridge\] invocations in different files +(possibly different crates) both contain function signatures involving the same +C++ type `example::Demo`. If both were written just containing `type Demo;`, +then both macro expansions would produce their own separate Rust type called +`Demo` and thus the compiler wouldn't allow us to take the `Demo` returned by +`file1::ffi::create_demo` and pass it as the `Demo` argument accepted by +`file2::ffi::take_ref_demo`. Instead, one of the two `Demo`s has been defined as +an extern type alias of the other, making them the same type in Rust. + +```rust,noplayground +// file1.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + unsafe extern "C++" { + type Demo; + + fn create_demo() -> UniquePtr; + } +} +``` + +```rust,noplayground +// file2.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + unsafe extern "C++" { + type Demo = crate::file1::ffi::Demo; + + fn take_ref_demo(demo: &Demo); + } +} +``` + +#### Integrating with bindgen-generated or handwritten unsafe bindings + +Handwritten `ExternType` impls make it possible to plug in a data structure +emitted by bindgen as the definition of a C++ type emitted by CXX. + +By writing the unsafe `ExternType` impl, the programmer asserts that the C++ +namespace and type name given in the type id refers to a C++ type that is +equivalent to Rust type that is the `Self` type of the impl. + +```rust,noplayground +mod folly_sys; // the bindgen-generated bindings + +use cxx::{type_id, ExternType}; + +unsafe impl ExternType for folly_sys::StringPiece { + type Id = type_id!("folly::StringPiece"); + type Kind = cxx::kind::Opaque; +} + +#[cxx::bridge(namespace = "folly")] +pub mod ffi { + unsafe extern "C++" { + include!("rust_cxx_bindings.h"); + + type StringPiece = crate::folly_sys::StringPiece; + + fn print_string_piece(s: &StringPiece); + } +} + +// Now if we construct a StringPiece or obtain one through one +// of the bindgen-generated signatures, we are able to pass it +// along to ffi::print_string_piece. +``` + +The `ExternType::Id` associated type encodes a type-level representation of the +type's C++ namespace and type name. It will always be defined using the +`type_id!` macro exposed in the cxx crate. + +The `ExternType::Kind` associated type will always be either +[`cxx::kind::Opaque`] or [`cxx::kind::Trivial`] identifying whether a C++ type +is soundly relocatable by Rust's move semantics. A C++ type is only okay to hold +and pass around by value in Rust if its [move constructor is trivial] and it has +no destructor. In CXX, these are called Trivial extern C++ types, while types +with nontrivial move behavior or a destructor must be considered Opaque and +handled by Rust only behind an indirection, such as a reference or UniquePtr. + +[`cxx::kind::Opaque`]: https://docs.rs/cxx/*/cxx/kind/enum.Opaque.html +[`cxx::kind::Trivial`]: https://docs.rs/cxx/*/cxx/kind/enum.Trivial.html +[move constructor is trivial]: https://en.cppreference.com/w/cpp/types/is_move_constructible + +If you believe your C++ type reflected by the ExternType impl is indeed fine to +hold by value and move in Rust, you can specify: + +```rust,noplayground +# unsafe impl cxx::ExternType for TypeName { +# type Id = cxx::type_id!("name::space::of::TypeName"); + type Kind = cxx::kind::Trivial; +# } +``` + +which will enable you to pass it into C++ functions by value, return it by +value, and include it in `struct`s that you have declared to `cxx::bridge`. Your +claim about the triviality of the C++ type will be checked by a `static_assert` +in the generated C++ side of the binding. + +## Explicit shim trait impls + +This is a somewhat niche feature, but important when you need it. + +CXX's support for C++'s std::unique\_ptr and std::vector is built on a set of +internal trait impls connecting the Rust API of UniquePtr and CxxVector to +underlying template instantiations performed by the C++ compiler. + +When reusing a binding type across multiple bridge modules as described in the +previous section, you may find that your code needs some trait impls which CXX +hasn't decided to generate. + +```rust,noplayground +#[cxx::bridge] +mod ffi1 { + extern "C++" { + include!("path/to/header.h"); + + type A; + type B; + + // Okay: CXX sees UniquePtr using a type B defined within the same + // bridge, and automatically emits the right template instantiations + // corresponding to std::unique_ptr. + fn get_b() -> UniquePtr; + } +} + +#[cxx::bridge] +mod ffi2 { + extern "C++" { + type A = crate::ffi1::A; + + // Rust trait error: CXX processing this module has no visibility into + // whether template instantiations corresponding to std::unique_ptr + // have already been emitted by the upstream library, so it does not + // emit them here. If the upstream library does not have any signatures + // involving UniquePtr, an explicit instantiation of the template + // needs to be requested in one module or the other. + fn get_a() -> UniquePtr; + } +} +``` + +You can request a specific template instantiation at a particular location in +the Rust crate hierarchy by writing `impl UniquePtr {}` inside of the bridge +module which defines `A` but does not otherwise contain any use of +`UniquePtr`. + +```rust,noplayground +#[cxx::bridge] +mod ffi1 { + extern "C++" { + include!("path/to/header.h"); + + type A; + type B; + + fn get_b() -> UniquePtr; + } + + impl UniquePtr {} // explicit instantiation +} +``` diff --git a/book/src/extern-rust.md b/book/src/extern-rust.md new file mode 100644 index 0000000..40f2237 --- /dev/null +++ b/book/src/extern-rust.md @@ -0,0 +1,165 @@ +{{#title extern "Rust" — Rust ♡ C++}} +# extern "Rust" + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + + } +} +``` + +The `extern "Rust"` section of a CXX bridge declares Rust types and signatures +to be made available to C++. + +The CXX code generator uses your extern "Rust" section(s) to produce a C++ +header file containing the corresponding C++ declarations. The generated header +has the same path as the Rust source file containing the bridge, except with a +`.rs.h` file extension. + +A bridge module may contain zero or more extern "Rust" blocks. + +## Opaque Rust types + +Types defined in Rust that are made available to C++, but only behind an +indirection. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type MyType; + type MyOtherType; + type OneMoreType<'a>; + } +# } +``` + +For example in the ***[Tutorial](tutorial.md)*** we saw `MultiBuf` used in this +way. Rust code created the `MultiBuf`, passed a `&mut MultiBuf` to C++, and C++ +later passed a `&mut MultiBuf` back across the bridge to Rust. + +Another example is the one on the ***[Box\](binding/box.md)*** page, which +exposes the Rust standard library's `std::fs::File` to C++ as an opaque type in +a similar way but with Box as the indirection rather than &mut. + +The types named as opaque types (`MyType` etc) refer to types in the `super` +module, the parent module of the CXX bridge. You can think of an opaque type `T` +as being like a re-export `use super::T` made available to C++ via the generated +header. + +Opaque types are currently required to be [`Sized`] and [`Unpin`]. In +particular, a trait object `dyn MyTrait` or slice `[T]` may not be used for an +opaque Rust type. These restrictions may be lifted in the future. + +[`Sized`]: https://doc.rust-lang.org/std/marker/trait.Sized.html +[`Unpin`]: https://doc.rust-lang.org/std/marker/trait.Unpin.html + +For now, types used as extern Rust types are required to be defined by the same +crate that contains the bridge using them. This restriction may be lifted in the +future. + +The bridge's parent module will contain the appropriate imports or definitions +for these types. + +```rust,noplayground +use path::to::MyType; + +pub struct MyOtherType { + ... +} +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MyType; +# type MyOtherType; +# } +# } +``` + +## Functions + +Rust functions made callable to C++. + +Just like for opaque types, these functions refer implicitly to something in +scope in the `super` module, whether defined there or imported by some `use` +statement. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MyType; + fn f() -> Box; + } +} + +struct MyType(i32); + +fn f() -> Box { + return Box::new(MyType(1)); +} +``` + +Extern Rust function signature may consist of types defined in the bridge, +primitives, and [any of these additional bindings](bindings.md). + +## Methods + +Any signature with a `self` parameter is interpreted as a Rust method and +exposed to C++ as a non-static member function. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type MyType; + fn f(&self) -> usize; + } +# } +``` + +The `self` parameter may be a shared reference `&self`, an exclusive reference +`&mut self`, or a pinned reference `self: Pin<&mut Self>`. A by-value `self` is +not currently supported. + +If the surrounding `extern "Rust"` block contains exactly one extern type, that +type is implicitly the receiver for a `&self` or `&mut self` method. If the +surrounding block contains *more than one* extern type, a receiver type must be +provided explicitly for the self parameter, or you can consider splitting into +multiple extern blocks. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type First; + type Second; + fn bar(self: &First); + fn foo(self: &mut Second); + } +# } +``` + +## Functions with explicit lifetimes + +An extern Rust function signature is allowed to contain explicit lifetimes but +in this case the function must be declared unsafe-to-call. This is pretty +meaningless given we're talking about calls from C++, but at least it draws some +extra attention from the caller that they may be responsible for upholding some +atypical lifetime relationship. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MyType; + unsafe fn f<'a>(&'a self, s: &str) -> &'a str; + } +} +``` + +Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are +type parameters or where-clauses. diff --git a/book/src/index.md b/book/src/index.md new file mode 100644 index 0000000..7add044 --- /dev/null +++ b/book/src/index.md @@ -0,0 +1,83 @@ +
    +githubcrates-iodocs-rs +
    + +# CXX — safe interop between Rust and C++ + +This library provides a safe mechanism for calling C++ code from Rust and Rust +code from C++. It carves out a regime of commonality where Rust and C++ are +semantically very similar and guides the programmer to express their language +boundary effectively within this regime. CXX fills in the low level stuff so +that you get a safe binding, preventing the pitfalls of doing a foreign function +interface over unsafe C-style signatures. + +
    + +
    + +From a high level description of the language boundary, CXX uses static analysis +of the types and function signatures to protect both Rust's and C++'s +invariants. Then it uses a pair of code generators to implement the boundary +efficiently on both sides together with any necessary static assertions for +later in the build process to verify correctness. + +The resulting FFI bridge operates at zero or negligible overhead, i.e. no +copying, no serialization, no memory allocation, no runtime checks needed. + +The FFI signatures are able to use native data structures from whichever side +they please. In addition, CXX provides builtin bindings for key standard library +types like strings, vectors, Box, unique\_ptr, etc to expose an idiomatic API on +those types to the other language. + +## Example + +In this example we are writing a Rust application that calls a C++ client of a +large-file blobstore service. The blobstore supports a `put` operation for a +discontiguous buffer upload. For example we might be uploading snapshots of a +circular buffer which would tend to consist of 2 pieces, or fragments of a file +spread across memory for some other reason (like a rope data structure). + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MultiBuf; + + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + include!("example/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr; + fn put(self: &BlobstoreClient, buf: &mut MultiBuf) -> Result; + } +} +``` + +Now we simply provide Rust definitions of all the things in the `extern "Rust"` +block and C++ definitions of all the things in the `extern "C++"` block, and get +to call back and forth safely. + +The [***Tutorial***](tutorial.md) chapter walks through a fleshed out version of +this blobstore example in full detail, including all of the Rust code and all of +the C++ code. The code is also provided in runnable form in the *demo* directory +of . To try it out, run `cargo run` from that +directory. + +- [demo/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo/src/main.rs) +- [demo/include/blobstore.h](https://github.com/dtolnay/cxx/blob/master/demo/include/blobstore.h) +- [demo/src/blobstore.cc](https://github.com/dtolnay/cxx/blob/master/demo/src/blobstore.cc) + +The key takeaway, which is enabled by the CXX library, is that the Rust code in +main.rs is 100% ordinary safe Rust code working idiomatically with Rust types +while the C++ code in blobstore.cc is 100% ordinary C++ code working +idiomatically with C++ types. The Rust code feels like Rust and the C++ code +feels like C++, not like C-style "FFI glue". + +
    + +***Chapter outline:** See the hamburger menu in the top left if you are on a +small screen and it didn't open with a sidebar by default.* diff --git a/book/src/overview.svg b/book/src/overview.svg new file mode 100644 index 0000000..df4fcf4 --- /dev/null +++ b/book/src/overview.svg @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/src/reference.md b/book/src/reference.md new file mode 100644 index 0000000..62ca35c --- /dev/null +++ b/book/src/reference.md @@ -0,0 +1,29 @@ +{{#title The bridge module — Rust ♡ C++}} +# The bridge module reference + +The ***[Core concepts](concepts.md)*** in chapter 2 covered the high level model +that CXX uses to represent a language boundary. This chapter builds on that one +to document an exhaustive reference on the syntax and functionality of +\#\[cxx::bridge\]. + +- ***[extern "Rust"](extern-rust.md)*** — exposing opaque Rust types, Rust + functions, Rust methods to C++; functions with lifetimes. + +- ***[extern "C++"](extern-c++.md)*** — binding opaque C++ types, C++ + functions, C++ member functions; sharing an opaque type definition across + multiple bridge modules or different crates; using bindgen-generated data + structures across a CXX bridge; Rust orphan-rule-compatible way to request + that particular glue code be emitted in a specific bridge module. + +- ***[Shared types](shared.md)*** — shared structs; shared enums; using + Rust as source of truth vs C++ as source of truth. + +- ***[Attributes](attributes.md)*** — working with namespaces; giving + functions a different name in their non-native language. + +- ***[Async functions](async.md)*** — integrating async C++ with async + Rust. + +- ***[Error handling](binding/result.md)*** — representing fallibility on + the language boundary; accessing a Rust error message from C++; customizing + the set of caught exceptions and their conversion to a Rust error message. diff --git a/book/src/shared.md b/book/src/shared.md new file mode 100644 index 0000000..4043db1 --- /dev/null +++ b/book/src/shared.md @@ -0,0 +1,246 @@ +{{#title Shared types — Rust ♡ C++}} +# Shared types + +Shared types enable *both* languages to have visibility into the internals of a +type. This is in contrast to opaque Rust types and opaque C++ types, for which +only one side gets to manipulate the internals. + +Unlike opaque types, the FFI bridge is allowed to pass and return shared types +by value. + +The order in which shared types are written is not important. C++ is order +sensitive but CXX will topologically sort and forward-declare your types as +necessary. + +## Shared structs and enums + +For enums, only C-like a.k.a. unit variants are currently supported. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + struct PlayingCard { + suit: Suit, + value: u8, // A=1, J=11, Q=12, K=13 + } + + enum Suit { + Clubs, + Diamonds, + Hearts, + Spades, + } + + unsafe extern "C++" { + fn deck() -> Vec; + fn sort(cards: &mut Vec); + } +} +``` + +## The generated data structures + +Shared structs compile to an aggregate-initialization compatible C++ struct. + +Shared enums compile to a C++ `enum class` with a sufficiently sized integral +base type decided by CXX. + +```cpp +// generated header + +struct PlayingCard final { + Suit suit; + uint8_t value; +}; + +enum class Suit : uint8_t { + Clubs = 0, + Diamonds = 1, + Hearts = 2, + Spades = 3, +}; +``` + +Because it is not UB in C++ for an `enum class` to hold a value different from +all of the listed variants, we use a Rust representation for shared enums that +is compatible with this. The API you'll get is something like: + +```rust,noplayground +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct Suit { + pub repr: u8, +} +#[allow(non_upper_case_globals)] +impl Suit { + pub const Clubs: Self = Suit { repr: 0 }; + pub const Diamonds: Self = Suit { repr: 1 }; + pub const Hearts: Self = Suit { repr: 2 }; + pub const Spades: Self = Suit { repr: 3 }; +} +``` + +Notice you're free to treat the enum as an integer in Rust code via the public +`repr` field. + +Pattern matching with `match` still works but will require you to write wildcard +arms to handle the situation of an enum value that is not one of the listed +variants. + +```rust,noplayground +fn main() { + let suit: Suit = /*...*/; + match suit { + Suit::Clubs => ..., + Suit::Diamonds => ..., + Suit::Hearts => ..., + Suit::Spades => ..., + _ => ..., // fallback arm + } +} +``` + +If a shared struct has generic lifetime parameters, the lifetimes are simply not +represented on the C++ side. C++ code will need care when working with borrowed +data (as usual in C++). + +```rust,noplayground +#[cxx::bridge] +mod ffi { + struct Borrowed<'a> { + flags: &'a [&'a str], + } +} +``` + +```cpp +// generated header + +struct Borrowed final { + rust::Slice flags; +}; +``` + +## Enum discriminants + +You may provide explicit discriminants for some or all of the enum variants, in +which case those numbers will be propagated into the generated C++ `enum class`. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + enum SmallPrime { + Two = 2, + Three = 3, + Five = 5, + Seven = 7, + } +} +``` + +Variants without an explicit discriminant are assigned the previous discriminant +plus 1. If the first variant has not been given an explicit discriminant, it is +assigned discriminant 0. + +By default CXX represents your enum using the smallest integer type capable of +fitting all the discriminants (whether explicit or implicit). If you need a +different representation for reasons, provide a `repr` attribute. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + #[repr(i32)] + enum Enum { + Zero, + One, + Five = 5, + Six, + } +} +``` + +```cpp +// generated header + +enum class Enum : int32_t { + Zero = 0, + One = 1, + Five = 5, + Six = 6, +}; +``` + +## Extern enums + +If you need to interoperate with an already existing enum for which an existing +C++ definition is the source of truth, make sure that definition is provided by +some header in the bridge and then declare your enum *additionally* as an extern +C++ type. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + enum Enum { + Yes, + No, + } + + extern "C++" { + include!("path/to/the/header.h"); + type Enum; + } +} +``` + +CXX will recognize this pattern and, instead of generating a C++ definition of +the enum, will instead generate C++ static assertions asserting that the +variants and discriminant values and integer representation written in Rust all +correctly match the existing C++ enum definition. + +Extern enums support all the same features as ordinary shared enums (explicit +discriminants, repr). Again, CXX will static assert that all of those things you +wrote are correct. + +## Derives + +The following standard traits are supported in `derive(...)` within the CXX +bridge module. + +- `Clone` +- `Copy` +- `Debug` +- `Default` +- `Eq` +- `Hash` +- `Ord` +- `PartialEq` +- `PartialOrd` + +Note that shared enums automatically always come with impls of `Copy`, `Clone`, +`Eq`, and `PartialEq`, so you're free to omit those derives on an enum. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + #[derive(Clone, Debug, Hash)] + struct ExampleStruct { + x: u32, + s: String, + } + + #[derive(Hash, Ord, PartialOrd)] + enum ExampleEnum { + Yes, + No, + } +} +``` + +The derives naturally apply to *both* the Rust data type *and* the corresponding +C++ data type: + +- `Hash` gives you a specialization of [`template <> struct std::hash`][hash] in C++ +- `PartialEq` produces `operator==` and `operator!=` +- `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=` + +[hash]: https://en.cppreference.com/w/cpp/utility/hash diff --git a/book/src/tutorial.md b/book/src/tutorial.md new file mode 100644 index 0000000..1182dc2 --- /dev/null +++ b/book/src/tutorial.md @@ -0,0 +1,692 @@ +{{#title Tutorial — Rust ♡ C++}} +# Tutorial: CXX blobstore client + +This example walks through a Rust application that calls into a C++ client of a +blobstore service. In fact we'll see calls going in both directions: Rust to C++ +as well as C++ to Rust. For your own use case it may be that you need just one +of these directions. + +All of the code involved in the example is shown on this page, but it's also +provided in runnable form in the *demo* directory of +. To try it out directly, run `cargo run` from +that directory. + +This tutorial assumes you've read briefly about **shared structs**, **opaque +types**, and **functions** in the [*Core concepts*](concepts.md) page. + +## Creating the project + +We'll use Cargo, which is the build system commonly used by open source Rust +projects. (CXX works with other build systems too; refer to chapter 5.) + +Create a blank Cargo project: `mkdir cxx-demo`; `cd cxx-demo`; `cargo init`. + +Edit the Cargo.toml to add a dependency on the `cxx` crate: + +```toml,hidelines=... +# Cargo.toml +...[package] +...name = "cxx-demo" +...version = "0.1.0" +...edition = "2021" + +[dependencies] +cxx = "1.0" +``` + +We'll revisit this Cargo.toml later when we get to compiling some C++ code. + +## Defining the language boundary + +CXX relies on a description of the function signatures that will be exposed from +each language to the other. You provide this description using `extern` blocks +in a Rust module annotated with the `#[cxx::bridge]` attribute macro. + +We'll open with just the following at the top of src/main.rs and walk through +each item in detail. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + +} +# +# fn main() {} +``` + +The contents of this module will be everything that needs to be agreed upon by +both sides of the FFI boundary. + +## Calling a C++ function from Rust + +Let's obtain an instance of the C++ blobstore client, a class `BlobstoreClient` +defined in C++. + +We'll treat `BlobstoreClient` as an *opaque type* in CXX's classification so +that Rust does not need to assume anything about its implementation, not even +its size or alignment. In general, a C++ type might have a move-constructor +which is incompatible with Rust's move semantics, or may hold internal +references which cannot be modeled by Rust's borrowing system. Though there are +alternatives, the easiest way to not care about any such thing on an FFI +boundary is to require no knowledge about a type by treating it as opaque. + +Opaque types may only be manipulated behind an indirection such as a reference +`&`, a Rust `Box`, or a `UniquePtr` (Rust binding of `std::unique_ptr`). We'll +add a function through which C++ can return a `std::unique_ptr` +to Rust. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("cxx-demo/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr; + } +} + +fn main() { + let client = ffi::new_blobstore_client(); +} +``` + +The nature of `unsafe` extern blocks is clarified in more detail in the +[*extern "C++"*](extern-c++.md) chapter. In brief: the programmer is **not** +promising that the signatures they have typed in are accurate; that would be +unreasonable. CXX performs static assertions that the signatures exactly match +what is declared in C++. Rather, the programmer is only on the hook for things +that C++'s semantics are not precise enough to capture, i.e. things that would +only be represented at most by comments in the C++ code. In this case, it's +whether `new_blobstore_client` is safe or unsafe to call. If that function said +something like "must be called at most once or we'll stomp yer memery", Rust +would instead want to expose it as `unsafe fn new_blobstore_client`, this time +inside a safe `extern "C++"` block because the programmer is no longer on the +hook for any safety claim about the signature. + +If you build this file right now with `cargo build`, it won't build because we +haven't written a C++ implementation of `new_blobstore_client` nor instructed +Cargo about how to link it into the resulting binary. You'll see an error from +the linker like this: + +```console +error: linking with `cc` failed: exit code: 1 + | + = /bin/ld: target/debug/deps/cxx-demo-7cb7fddf3d67d880.rcgu.o: in function `cxx_demo::ffi::new_blobstore_client': + src/main.rs:1: undefined reference to `cxxbridge1$new_blobstore_client' + collect2: error: ld returned 1 exit status +``` + +## Adding in the C++ code + +In CXX's integration with Cargo, all #include paths begin with a crate name by +default (when not explicitly selected otherwise by a crate; see +`CFG.include_prefix` in chapter 5). That's why we see +`include!("cxx-demo/include/blobstore.h")` above — we'll be putting the +C++ header at relative path `include/blobstore.h` within the Rust crate. If your +crate is named something other than `cxx-demo` according to the `name` field in +Cargo.toml, you will need to use that name everywhere in place of `cxx-demo` +throughout this tutorial. + +```cpp +// include/blobstore.h + +#pragma once +#include + +class BlobstoreClient { +public: + BlobstoreClient(); +}; + +std::unique_ptr new_blobstore_client(); +``` + +```cpp +// src/blobstore.cc + +#include "cxx-demo/include/blobstore.h" + +BlobstoreClient::BlobstoreClient() {} + +std::unique_ptr new_blobstore_client() { + return std::unique_ptr(new BlobstoreClient()); +} +``` + +Using `std::make_unique` would work too, as long as you pass `std("c++14")` to +the C++ compiler as described later on. + +The placement in *include/* and *src/* is not significant; you can place C++ +code anywhere else in the crate as long as you use the right paths throughout +the tutorial. + +Be aware that *CXX does not look at any of these files.* You're free to put +arbitrary C++ code in here, #include your own libraries, etc. All we do is emit +static assertions against what you provide in the headers. + +## Compiling the C++ code with Cargo + +Cargo has a [build scripts] feature suitable for compiling non-Rust code. + +We need to introduce a new build-time dependency on CXX's C++ code generator in +Cargo.toml: + +```toml,hidelines=... +# Cargo.toml +...[package] +...name = "cxx-demo" +...version = "0.1.0" +...edition = "2021" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +Then add a build.rs build script adjacent to Cargo.toml to run the cxx-build +code generator and C++ compiler. The relevant arguments are the path to the Rust +source file containing the cxx::bridge language boundary definition, and the +paths to any additional C++ source files to be compiled during the Rust crate's +build. + +```rust,noplayground +// build.rs + +fn main() { + cxx_build::bridge("src/main.rs") + .file("src/blobstore.cc") + .compile("cxx-demo"); + + println!("cargo:rerun-if-changed=src/main.rs"); + println!("cargo:rerun-if-changed=src/blobstore.cc"); + println!("cargo:rerun-if-changed=include/blobstore.h"); +} +``` + +This build.rs would also be where you set up C++ compiler flags, for example if +you'd like to have access to `std::make_unique` from C++14. See the page on +***[Cargo-based builds](build/cargo.md)*** for more details about CXX's Cargo +integration. + +```rust,noplayground +# // build.rs +# +# fn main() { + cxx_build::bridge("src/main.rs") + .file("src/blobstore.cc") + .std("c++14") + .compile("cxx-demo"); +# } +``` + +[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html + +The project should now build and run successfully, though not do anything useful +yet. + +```console +cxx-demo$ cargo run + Compiling cxx-demo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.34s + Running `target/debug/cxx-demo` + +cxx-demo$ +``` + +## Calling a Rust function from C++ + +Our C++ blobstore supports a `put` operation for a discontiguous buffer upload. +For example we might be uploading snapshots of a circular buffer which would +tend to consist of 2 pieces, or fragments of a file spread across memory for +some other reason (like a rope data structure). + +We'll express this by handing off an iterator over contiguous borrowed chunks. +This loosely resembles the API of the widely used `bytes` crate's `Buf` trait. +During a `put`, we'll make C++ call back into Rust to obtain contiguous chunks +of the upload (all with no copying or allocation on the language boundary). In +reality the C++ client might contain some sophisticated batching of chunks +and/or parallel uploading that all of this ties into. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MultiBuf; + + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + include!("cxx-demo/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr; + fn put(&self, parts: &mut MultiBuf) -> u64; + } +} +# +# fn main() { +# let client = ffi::new_blobstore_client(); +# } +``` + +Any signature having a `self` parameter (the Rust name for C++'s `this`) is +considered a method / non-static member function. If there is only one `type` in +the surrounding extern block, it'll be a method of that type. If there is more +than one `type`, you can disambiguate which one a method belongs to by writing +`self: &BlobstoreClient` in the argument list. + +As usual, now we need to provide Rust definitions of everything declared by the +`extern "Rust"` block and a C++ definition of the new signature declared by the +`extern "C++"` block. + +```rust,noplayground +// src/main.rs +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { +# include!("cxx-demo/include/blobstore.h"); +# +# type BlobstoreClient; +# +# fn new_blobstore_client() -> UniquePtr; +# fn put(&self, parts: &mut MultiBuf) -> u64; +# } +# } + +// An iterator over contiguous chunks of a discontiguous file object. Toy +// implementation uses a Vec> but in reality this might be iterating +// over some more complex Rust data structure like a rope, or maybe loading +// chunks lazily from somewhere. +pub struct MultiBuf { + chunks: Vec>, + pos: usize, +} + +pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { + let next = buf.chunks.get(buf.pos); + buf.pos += 1; + next.map_or(&[], Vec::as_slice) +} +# +# fn main() { +# let client = ffi::new_blobstore_client(); +# } +``` + +```cpp,hidelines=... +// include/blobstore.h + +...#pragma once +...#include +... +struct MultiBuf; + +class BlobstoreClient { +public: + BlobstoreClient(); + uint64_t put(MultiBuf &buf) const; +}; +... +...std::unique_ptr new_blobstore_client(); +``` + +In blobstore.cc we're able to call the Rust `next_chunk` function, exposed to +C++ by a header `main.rs.h` generated by the CXX code generator. In CXX's Cargo +integration this generated header has a path containing the crate name, the +relative path of the Rust source file within the crate, and a `.rs.h` extension. + +```cpp,hidelines=... +// src/blobstore.cc + +#include "cxx-demo/include/blobstore.h" +#include "cxx-demo/src/main.rs.h" +#include +#include +... +...BlobstoreClient::BlobstoreClient() {} +... +...std::unique_ptr new_blobstore_client() { +... return std::make_unique(); +...} + +// Upload a new blob and return a blobid that serves as a handle to the blob. +uint64_t BlobstoreClient::put(MultiBuf &buf) const { + // Traverse the caller's chunk iterator. + std::string contents; + while (true) { + auto chunk = next_chunk(buf); + if (chunk.size() == 0) { + break; + } + contents.append(reinterpret_cast(chunk.data()), chunk.size()); + } + + // Pretend we did something useful to persist the data. + auto blobid = std::hash{}(contents); + return blobid; +} +``` + +This is now ready to use. :) + +```rust,noplayground +// src/main.rs +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { +# include!("cxx-demo/include/blobstore.h"); +# +# type BlobstoreClient; +# +# fn new_blobstore_client() -> UniquePtr; +# fn put(&self, parts: &mut MultiBuf) -> u64; +# } +# } +# +# pub struct MultiBuf { +# chunks: Vec>, +# pos: usize, +# } +# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { +# let next = buf.chunks.get(buf.pos); +# buf.pos += 1; +# next.map_or(&[], Vec::as_slice) +# } + +fn main() { + let client = ffi::new_blobstore_client(); + + // Upload a blob. + let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; + let mut buf = MultiBuf { chunks, pos: 0 }; + let blobid = client.put(&mut buf); + println!("blobid = {}", blobid); +} +``` + +```console +cxx-demo$ cargo run + Compiling cxx-demo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/cxx-demo` + +blobid = 9851996977040795552 +``` + +## Interlude: What gets generated? + +For the curious, it's easy to look behind the scenes at what CXX has done to +make these function calls work. You shouldn't need to do this during normal +usage of CXX, but for the purpose of this tutorial it can be educative. + +CXX comprises *two* code generators: a Rust one (which is the cxx::bridge +attribute procedural macro) and a C++ one. + +### Rust generated code + +It's easiest to view the output of the procedural macro by installing +[cargo-expand]. Then run `cargo expand ::ffi` to macro-expand the `mod ffi` +module. + +[cargo-expand]: https://github.com/dtolnay/cargo-expand + +```console +cxx-demo$ cargo install cargo-expand +cxx-demo$ cargo expand ::ffi +``` + +You'll see some deeply unpleasant code involving `#[repr(C)]`, `#[link_name]`, +and `#[export_name]`. + +### C++ generated code + +For debugging convenience, `cxx_build` links all generated C++ code into Cargo's +target directory under *target/cxxbridge/*. + +```console +cxx-demo$ exa -T target/cxxbridge/ +target/cxxbridge +├── cxx-demo +│ └── src +│ ├── main.rs.cc -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc +│ └── main.rs.h -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h +└── rust + └── cxx.h -> ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h +``` + +In those files you'll see declarations or templates of any CXX Rust types +present in your language boundary (like `rust::Slice` for `&[T]`) and `extern +"C"` signatures corresponding to your extern functions. + +If it fits your workflow better, the CXX C++ code generator is also available as +a standalone executable which outputs generated code to stdout. + +```console +cxx-demo$ cargo install cxxbridge-cmd +cxx-demo$ cxxbridge src/main.rs +``` + +## Shared data structures + +So far the calls in both directions above only used **opaque types**, not +**shared structs**. + +Shared structs are data structures whose complete definition is visible to both +languages, making it possible to pass them by value across the language +boundary. Shared structs translate to a C++ aggregate-initialization compatible +struct exactly matching the layout of the Rust one. + +As the last step of this demo, we'll use a shared struct `BlobMetadata` to pass +metadata about blobs between our Rust application and C++ blobstore client. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct BlobMetadata { + size: usize, + tags: Vec, + } + + extern "Rust" { + // ... +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + // ... +# include!("cxx-demo/include/blobstore.h"); +# +# type BlobstoreClient; +# +# fn new_blobstore_client() -> UniquePtr; +# fn put(&self, parts: &mut MultiBuf) -> u64; + fn tag(&self, blobid: u64, tag: &str); + fn metadata(&self, blobid: u64) -> BlobMetadata; + } +} +# +# pub struct MultiBuf { +# chunks: Vec>, +# pos: usize, +# } +# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { +# let next = buf.chunks.get(buf.pos); +# buf.pos += 1; +# next.map_or(&[], Vec::as_slice) +# } + +fn main() { + let client = ffi::new_blobstore_client(); + + // Upload a blob. + let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; + let mut buf = MultiBuf { chunks, pos: 0 }; + let blobid = client.put(&mut buf); + println!("blobid = {}", blobid); + + // Add a tag. + client.tag(blobid, "rust"); + + // Read back the tags. + let metadata = client.metadata(blobid); + println!("tags = {:?}", metadata.tags); +} +``` + +```cpp,hidelines=... +// include/blobstore.h + +#pragma once +#include "rust/cxx.h" +...#include + +struct MultiBuf; +struct BlobMetadata; + +class BlobstoreClient { +public: + BlobstoreClient(); + uint64_t put(MultiBuf &buf) const; + void tag(uint64_t blobid, rust::Str tag) const; + BlobMetadata metadata(uint64_t blobid) const; + +private: + class impl; + std::shared_ptr impl; +}; +... +...std::unique_ptr new_blobstore_client(); +``` + +```cpp,hidelines=... +// src/blobstore.cc + +#include "cxx-demo/include/blobstore.h" +#include "cxx-demo/src/main.rs.h" +#include +#include +#include +#include +#include + +// Toy implementation of an in-memory blobstore. +// +// In reality the implementation of BlobstoreClient could be a large +// complex C++ library. +class BlobstoreClient::impl { + friend BlobstoreClient; + using Blob = struct { + std::string data; + std::set tags; + }; + std::unordered_map blobs; +}; + +BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {} +... +...// Upload a new blob and return a blobid that serves as a handle to the blob. +...uint64_t BlobstoreClient::put(MultiBuf &buf) const { +... // Traverse the caller's chunk iterator. +... std::string contents; +... while (true) { +... auto chunk = next_chunk(buf); +... if (chunk.size() == 0) { +... break; +... } +... contents.append(reinterpret_cast(chunk.data()), chunk.size()); +... } +... +... // Insert into map and provide caller the handle. +... auto blobid = std::hash{}(contents); +... impl->blobs[blobid] = {std::move(contents), {}}; +... return blobid; +...} + +// Add tag to an existing blob. +void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const { + impl->blobs[blobid].tags.emplace(tag); +} + +// Retrieve metadata about a blob. +BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const { + BlobMetadata metadata{}; + auto blob = impl->blobs.find(blobid); + if (blob != impl->blobs.end()) { + metadata.size = blob->second.data.size(); + std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), + [&](auto &t) { metadata.tags.emplace_back(t); }); + } + return metadata; +} +... +...std::unique_ptr new_blobstore_client() { +... return std::make_unique(); +...} +``` + +```console +cxx-demo$ cargo run + Running `target/debug/cxx-demo` + +blobid = 9851996977040795552 +tags = ["rust"] +``` + +*You've now seen all the code involved in the tutorial. It's available all +together in runnable form in the* demo *directory of +. You can run it directly without stepping +through the steps above by running `cargo run` from that directory.* + +
    + +# Takeaways + +The key contribution of CXX is it gives you Rust–C++ interop in which +*all* of the Rust side of the code you write *really* looks like you are just +writing normal Rust, and the C++ side *really* looks like you are just writing +normal C++. + +You've seen in this tutorial that none of the code involved feels like C or like +the usual perilous "FFI glue" prone to leaks or memory safety flaws. + +An expressive system of opaque types, shared types, and key standard library +type bindings enables API design on the language boundary that captures the +proper ownership and borrowing contracts of the interface. + +CXX plays to the strengths of the Rust type system *and* C++ type system *and* +the programmer's intuitions. An individual working on the C++ side without a +Rust background, or the Rust side without a C++ background, will be able to +apply all their usual intuitions and best practices about development in their +language to maintain a correct FFI. + +

    diff --git a/book/theme/head.hbs b/book/theme/head.hbs new file mode 100644 index 0000000..d6b32cb --- /dev/null +++ b/book/theme/head.hbs @@ -0,0 +1,7 @@ + + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2fbb018 --- /dev/null +++ b/build.rs @@ -0,0 +1,75 @@ +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() { + let manifest_dir_opt = env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from); + let manifest_dir = manifest_dir_opt.as_deref().unwrap_or(Path::new("")); + + cc::Build::new() + .file(manifest_dir.join("src/cxx.cc")) + .cpp(true) + .cpp_link_stdlib(None) // linked via link-cplusplus crate + .std(cxxbridge_flags::STD) + .warnings_into_errors(cfg!(deny_warnings)) + .compile("cxxbridge1"); + + println!("cargo:rerun-if-changed=src/cxx.cc"); + println!("cargo:rerun-if-changed=include/cxx.h"); + println!("cargo:rustc-cfg=built_with_cargo"); + + if let Some(manifest_dir) = &manifest_dir_opt { + let cxx_h = manifest_dir.join("include").join("cxx.h"); + println!("cargo:HEADER={}", cxx_h.to_string_lossy()); + } + + if let Some(rustc) = rustc_version() { + if rustc.minor >= 80 { + println!("cargo:rustc-check-cfg=cfg(built_with_cargo)"); + println!("cargo:rustc-check-cfg=cfg(compile_error_if_alloc)"); + println!("cargo:rustc-check-cfg=cfg(compile_error_if_std)"); + println!("cargo:rustc-check-cfg=cfg(cxx_experimental_no_alloc)"); + println!("cargo:rustc-check-cfg=cfg(error_in_core)"); + println!("cargo:rustc-check-cfg=cfg(seek_relative)"); + println!("cargo:rustc-check-cfg=cfg(skip_ui_tests)"); + } + + if rustc.minor < 73 { + println!("cargo:warning=The cxx crate requires a rustc version 1.73.0 or newer."); + println!( + "cargo:warning=You appear to be building with: {}", + rustc.version, + ); + } + + if rustc.minor >= 80 { + // std::io::Seek::seek_relative + println!("cargo:rustc-cfg=seek_relative"); + } + + if rustc.minor >= 81 { + // core::error::Error + println!("cargo:rustc-cfg=error_in_core"); + } + } +} + +struct RustVersion { + version: String, + minor: u32, +} + +fn rustc_version() -> Option { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = String::from_utf8(output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + let minor = pieces.next()?.parse().ok()?; + Some(RustVersion { version, minor }) +} diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..c24e3b5 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1 @@ +-std=c++11 diff --git a/include/cxx.h b/include/cxx.h new file mode 100644 index 0000000..4e261a3 --- /dev/null +++ b/include/cxx.h @@ -0,0 +1,1149 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) +#include +#else +#include +#endif + +#if __cplusplus >= 201703L +#include +#endif + +#if __cplusplus >= 202002L +#include +#endif + +namespace rust { +inline namespace cxxbridge1 { + +struct unsafe_bitcopy_t; + +namespace { +template +class impl; +} + +#ifndef CXXBRIDGE1_RUST_STRING +#define CXXBRIDGE1_RUST_STRING +// https://cxx.rs/binding/string.html +class String final { +public: + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + String(const std::string &); + String(const char *); + String(const char *, std::size_t); + String(const char16_t *); + String(const char16_t *, std::size_t); +#ifdef __cpp_char8_t + String(const char8_t *s); + String(const char8_t *s, std::size_t len); +#endif + + // Replace invalid Unicode data with the replacement character (U+FFFD). + static String lossy(const std::string &) noexcept; + static String lossy(const char *) noexcept; + static String lossy(const char *, std::size_t) noexcept; + static String lossy(const char16_t *) noexcept; + static String lossy(const char16_t *, std::size_t) noexcept; + + String &operator=(const String &) & noexcept; + String &operator=(String &&) & noexcept; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + const char *c_str() noexcept; + + std::size_t capacity() const noexcept; + void reserve(size_t new_cap) noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; + + // Internal API only intended for the cxxbridge code generator. + String(unsafe_bitcopy_t, const String &) noexcept; + +private: + struct lossy_t; + String(lossy_t, const char *, std::size_t) noexcept; + String(lossy_t, const char16_t *, std::size_t) noexcept; + friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } + + // Size and alignment statically verified by rust_string.rs. + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STRING + +#ifndef CXXBRIDGE1_RUST_STR +#define CXXBRIDGE1_RUST_STR +// https://cxx.rs/binding/str.html +class Str final { +public: + Str() noexcept; + Str(const String &) noexcept; + Str(const std::string &); + Str(const char *); + Str(const char *, std::size_t); + + Str &operator=(const Str &) & noexcept = default; + + explicit operator std::string() const; +#if __cplusplus >= 201703L + explicit operator std::string_view() const; +#endif + + // Note: no null terminator. + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + // Important in order for System V ABI to pass in registers. + Str(const Str &) noexcept = default; + ~Str() noexcept = default; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; + +private: + class uninit; + Str(uninit) noexcept; + friend impl; + + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STR + +#ifndef CXXBRIDGE1_RUST_SLICE +namespace detail { +template +struct copy_assignable_if {}; + +template <> +struct copy_assignable_if { + copy_assignable_if() noexcept = default; + copy_assignable_if(const copy_assignable_if &) noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default; +}; +} // namespace detail + +// https://cxx.rs/binding/slice.html +template +class Slice final + : private detail::copy_assignable_if::value> { +public: + using value_type = T; + + Slice() noexcept; + Slice(T *, std::size_t count) noexcept; + + template + explicit Slice(C &c) : Slice(c.data(), c.size()) {} + + Slice &operator=(const Slice &) & noexcept = default; + Slice &operator=(Slice &&) & noexcept = default; + + T *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](std::size_t n) const noexcept; + T &at(std::size_t n) const; + T &front() const noexcept; + T &back() const noexcept; + + // Important in order for System V ABI to pass in registers. + Slice(const Slice &) noexcept = default; + ~Slice() noexcept = default; + + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; + +private: + class uninit; + Slice(uninit) noexcept; + friend impl; + friend void sliceInit(void *, const void *, std::size_t) noexcept; + friend void *slicePtr(const void *) noexcept; + friend std::size_t sliceLen(const void *) noexcept; + + std::array repr; +}; + +#ifdef __cpp_deduction_guides +template +explicit Slice(C &c) + -> Slice().data())>>; +#endif // __cpp_deduction_guides + +template +class Slice::iterator final { +public: +#if __cplusplus >= 202002L + using iterator_category = std::contiguous_iterator_tag; +#else + using iterator_category = std::random_access_iterator_tag; +#endif + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = typename std::add_pointer::type; + using reference = typename std::add_lvalue_reference::type; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + reference operator[](difference_type) const noexcept; + + iterator &operator++() noexcept; + iterator operator++(int) noexcept; + iterator &operator--() noexcept; + iterator operator--(int) noexcept; + + iterator &operator+=(difference_type) noexcept; + iterator &operator-=(difference_type) noexcept; + iterator operator+(difference_type) const noexcept; + friend inline iterator operator+(difference_type lhs, iterator rhs) noexcept { + return rhs + lhs; + } + iterator operator-(difference_type) const noexcept; + difference_type operator-(const iterator &) const noexcept; + + bool operator==(const iterator &) const noexcept; + bool operator!=(const iterator &) const noexcept; + bool operator<(const iterator &) const noexcept; + bool operator<=(const iterator &) const noexcept; + bool operator>(const iterator &) const noexcept; + bool operator>=(const iterator &) const noexcept; + +private: + friend class Slice; + void *pos; + std::size_t stride; +}; + +#if __cplusplus >= 202002L +static_assert(std::ranges::contiguous_range>); +static_assert(std::contiguous_iterator::iterator>); +#endif + +#endif // CXXBRIDGE1_RUST_SLICE + +#ifndef CXXBRIDGE1_RUST_BOX +// https://cxx.rs/binding/box.html +template +class Box final { +public: + using element_type = T; + using const_pointer = + typename std::add_pointer::type>::type; + using pointer = typename std::add_pointer::type; + + Box() = delete; + Box(Box &&) noexcept; + ~Box() noexcept; + + explicit Box(const T &); + explicit Box(T &&); + + Box &operator=(Box &&) & noexcept; + + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; + + template + static Box in_place(Fields &&...); + + void swap(Box &) noexcept; + + // Important: requires that `raw` came from an into_raw call. Do not pass a + // pointer from `new` or any other source. + static Box from_raw(T *) noexcept; + + T *into_raw() noexcept; + + /* Deprecated */ using value_type = element_type; + +private: + class uninit; + class allocation; + Box(uninit) noexcept; + void drop() noexcept; + + friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } + + T *ptr; +}; +#endif // CXXBRIDGE1_RUST_BOX + +#ifndef CXXBRIDGE1_RUST_VEC +// https://cxx.rs/binding/vec.html +template +class Vec final { +public: + using value_type = T; + + Vec() noexcept; + Vec(std::initializer_list); + Vec(const Vec &); + Vec(Vec &&) noexcept; + ~Vec() noexcept; + + Vec &operator=(Vec &&) & noexcept; + Vec &operator=(const Vec &) &; + + std::size_t size() const noexcept; + bool empty() const noexcept; + const T *data() const noexcept; + T *data() noexcept; + std::size_t capacity() const noexcept; + + const T &operator[](std::size_t n) const noexcept; + const T &at(std::size_t n) const; + const T &front() const noexcept; + const T &back() const noexcept; + + T &operator[](std::size_t n) noexcept; + T &at(std::size_t n); + T &front() noexcept; + T &back() noexcept; + + void reserve(std::size_t new_cap); + void push_back(const T &value); + void push_back(T &&value); + template + void emplace_back(Args &&...args); + void truncate(std::size_t len); + void clear(); + + using iterator = typename Slice::iterator; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = typename Slice::iterator; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + void swap(Vec &) noexcept; + + // Internal API only intended for the cxxbridge code generator. + Vec(unsafe_bitcopy_t, const Vec &) noexcept; + +private: + void reserve_total(std::size_t new_cap) noexcept; + void set_len(std::size_t len) noexcept; + void drop() noexcept; + + friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); } + + // Size and alignment statically verified by rust_vec.rs. + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_VEC + +#ifndef CXXBRIDGE1_RUST_FN +// https://cxx.rs/binding/fn.html +template +class Fn; + +template +class Fn final { +public: + Ret operator()(Args... args) const noexcept; + Fn operator*() const noexcept; + +private: + Ret (*trampoline)(Args..., void *fn) noexcept; + void *fn; +}; +#endif // CXXBRIDGE1_RUST_FN + +#ifndef CXXBRIDGE1_RUST_ERROR +#define CXXBRIDGE1_RUST_ERROR +// https://cxx.rs/binding/result.html +class Error final : public std::exception { +public: + Error(const Error &); + Error(Error &&) noexcept; + ~Error() noexcept override; + + Error &operator=(const Error &) &; + Error &operator=(Error &&) & noexcept; + + const char *what() const noexcept override; + +private: + Error() noexcept = default; + friend impl; + const char *msg; + std::size_t len; +}; +#endif // CXXBRIDGE1_RUST_ERROR + +#ifndef CXXBRIDGE1_RUST_ISIZE +#define CXXBRIDGE1_RUST_ISIZE +#if defined(_WIN32) +using isize = SSIZE_T; +#else +using isize = ssize_t; +#endif +#endif // CXXBRIDGE1_RUST_ISIZE + +std::ostream &operator<<(std::ostream &, const String &); +std::ostream &operator<<(std::ostream &, const Str &); + +#ifndef CXXBRIDGE1_RUST_OPAQUE +#define CXXBRIDGE1_RUST_OPAQUE +// Base class of generated opaque Rust types. +class Opaque { +public: + Opaque() = delete; + Opaque(const Opaque &) = delete; + ~Opaque() = delete; +}; +#endif // CXXBRIDGE1_RUST_OPAQUE + +template +std::size_t size_of(); +template +std::size_t align_of(); + +// IsRelocatable is used in assertions that a C++ type passed by value +// between Rust and C++ is soundly relocatable by Rust. +// +// There may be legitimate reasons to opt out of the check for support of types +// that the programmer knows are soundly Rust-movable despite not being +// recognized as such by the C++ type system due to a move constructor or +// destructor. To opt out of the relocatability check, do either of the +// following things in any header used by `include!` in the bridge. +// +// --- if you define the type: +// struct MyType { +// ... +// + using IsRelocatable = std::true_type; +// }; +// +// --- otherwise: +// + template <> +// + struct rust::IsRelocatable : std::true_type {}; +template +struct IsRelocatable; + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; +using usize = std::size_t; // see static asserts in cxx.cc +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; +using f32 = float; +using f64 = double; + +// Snake case aliases for use in code that uses this style for type names. +using string = String; +using str = Str; +template +using slice = Slice; +template +using box = Box; +template +using vec = Vec; +using error = Error; +template +using fn = Fn; +template +using is_relocatable = IsRelocatable; + + + +//////////////////////////////////////////////////////////////////////////////// +/// end public API, begin implementation details + +#ifndef CXXBRIDGE1_PANIC +#define CXXBRIDGE1_PANIC +template +void panic [[noreturn]] (const char *msg); +#endif // CXXBRIDGE1_PANIC + +#ifndef CXXBRIDGE1_RUST_FN +#define CXXBRIDGE1_RUST_FN +template +Ret Fn::operator()(Args... args) const noexcept { + return (*this->trampoline)(std::forward(args)..., this->fn); +} + +template +Fn Fn::operator*() const noexcept { + return *this; +} +#endif // CXXBRIDGE1_RUST_FN + +#ifndef CXXBRIDGE1_RUST_BITCOPY_T +#define CXXBRIDGE1_RUST_BITCOPY_T +struct unsafe_bitcopy_t final { + explicit unsafe_bitcopy_t() = default; +}; +#endif // CXXBRIDGE1_RUST_BITCOPY_T + +#ifndef CXXBRIDGE1_RUST_BITCOPY +#define CXXBRIDGE1_RUST_BITCOPY +constexpr unsafe_bitcopy_t unsafe_bitcopy{}; +#endif // CXXBRIDGE1_RUST_BITCOPY + +#ifndef CXXBRIDGE1_RUST_SLICE +#define CXXBRIDGE1_RUST_SLICE +template +Slice::Slice() noexcept { + sliceInit(this, reinterpret_cast(align_of()), 0); +} + +template +Slice::Slice(T *s, std::size_t count) noexcept { + assert(s != nullptr || count == 0); + sliceInit(this, + s == nullptr && count == 0 + ? reinterpret_cast(align_of()) + : const_cast::type *>(s), + count); +} + +template +T *Slice::data() const noexcept { + return reinterpret_cast(slicePtr(this)); +} + +template +std::size_t Slice::size() const noexcept { + return sliceLen(this); +} + +template +std::size_t Slice::length() const noexcept { + return this->size(); +} + +template +bool Slice::empty() const noexcept { + return this->size() == 0; +} + +template +T &Slice::operator[](std::size_t n) const noexcept { + assert(n < this->size()); + auto ptr = static_cast(slicePtr(this)) + size_of() * n; + return *reinterpret_cast(ptr); +} + +template +T &Slice::at(std::size_t n) const { + if (n >= this->size()) { + panic("rust::Slice index out of range"); + } + return (*this)[n]; +} + +template +T &Slice::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template +T &Slice::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template +typename Slice::iterator::reference +Slice::iterator::operator*() const noexcept { + return *static_cast(this->pos); +} + +template +typename Slice::iterator::pointer +Slice::iterator::operator->() const noexcept { + return static_cast(this->pos); +} + +template +typename Slice::iterator::reference Slice::iterator::operator[]( + typename Slice::iterator::difference_type n) const noexcept { + auto ptr = static_cast(this->pos) + this->stride * n; + return *reinterpret_cast(ptr); +} + +template +typename Slice::iterator &Slice::iterator::operator++() noexcept { + this->pos = static_cast(this->pos) + this->stride; + return *this; +} + +template +typename Slice::iterator Slice::iterator::operator++(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast(this->pos) + this->stride; + return ret; +} + +template +typename Slice::iterator &Slice::iterator::operator--() noexcept { + this->pos = static_cast(this->pos) - this->stride; + return *this; +} + +template +typename Slice::iterator Slice::iterator::operator--(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast(this->pos) - this->stride; + return ret; +} + +template +typename Slice::iterator &Slice::iterator::operator+=( + typename Slice::iterator::difference_type n) noexcept { + this->pos = static_cast(this->pos) + this->stride * n; + return *this; +} + +template +typename Slice::iterator &Slice::iterator::operator-=( + typename Slice::iterator::difference_type n) noexcept { + this->pos = static_cast(this->pos) - this->stride * n; + return *this; +} + +template +typename Slice::iterator Slice::iterator::operator+( + typename Slice::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast(this->pos) + this->stride * n; + return ret; +} + +template +typename Slice::iterator Slice::iterator::operator-( + typename Slice::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast(this->pos) - this->stride * n; + return ret; +} + +template +typename Slice::iterator::difference_type +Slice::iterator::operator-(const iterator &other) const noexcept { + auto diff = std::distance(static_cast(other.pos), + static_cast(this->pos)); + return diff / static_cast::iterator::difference_type>( + this->stride); +} + +template +bool Slice::iterator::operator==(const iterator &other) const noexcept { + return this->pos == other.pos; +} + +template +bool Slice::iterator::operator!=(const iterator &other) const noexcept { + return this->pos != other.pos; +} + +template +bool Slice::iterator::operator<(const iterator &other) const noexcept { + return this->pos < other.pos; +} + +template +bool Slice::iterator::operator<=(const iterator &other) const noexcept { + return this->pos <= other.pos; +} + +template +bool Slice::iterator::operator>(const iterator &other) const noexcept { + return this->pos > other.pos; +} + +template +bool Slice::iterator::operator>=(const iterator &other) const noexcept { + return this->pos >= other.pos; +} + +template +typename Slice::iterator Slice::begin() const noexcept { + iterator it; + it.pos = slicePtr(this); + it.stride = size_of(); + return it; +} + +template +typename Slice::iterator Slice::end() const noexcept { + iterator it = this->begin(); + it.pos = static_cast(it.pos) + it.stride * this->size(); + return it; +} + +template +void Slice::swap(Slice &rhs) noexcept { + std::swap(*this, rhs); +} +#endif // CXXBRIDGE1_RUST_SLICE + +#ifndef CXXBRIDGE1_RUST_BOX +#define CXXBRIDGE1_RUST_BOX +template +class Box::uninit {}; + +template +class Box::allocation { + static T *alloc() noexcept; + static void dealloc(T *) noexcept; + +public: + allocation() noexcept : ptr(alloc()) {} + ~allocation() noexcept { + if (this->ptr) { + dealloc(this->ptr); + } + } + T *ptr; +}; + +template +Box::Box(Box &&other) noexcept : ptr(other.ptr) { + other.ptr = nullptr; +} + +template +Box::Box(const T &val) { + allocation alloc; + ::new (alloc.ptr) T(val); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template +Box::Box(T &&val) { + allocation alloc; + ::new (alloc.ptr) T(std::move(val)); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template +Box::~Box() noexcept { + if (this->ptr) { + this->drop(); + } +} + +template +Box &Box::operator=(Box &&other) & noexcept { + if (this->ptr) { + this->drop(); + } + this->ptr = other.ptr; + other.ptr = nullptr; + return *this; +} + +template +const T *Box::operator->() const noexcept { + return this->ptr; +} + +template +const T &Box::operator*() const noexcept { + return *this->ptr; +} + +template +T *Box::operator->() noexcept { + return this->ptr; +} + +template +T &Box::operator*() noexcept { + return *this->ptr; +} + +template +template +Box Box::in_place(Fields &&...fields) { + allocation alloc; + auto ptr = alloc.ptr; + ::new (ptr) T{std::forward(fields)...}; + alloc.ptr = nullptr; + return from_raw(ptr); +} + +template +void Box::swap(Box &rhs) noexcept { + using std::swap; + swap(this->ptr, rhs.ptr); +} + +template +Box Box::from_raw(T *raw) noexcept { + Box box = uninit{}; + box.ptr = raw; + return box; +} + +template +T *Box::into_raw() noexcept { + T *raw = this->ptr; + this->ptr = nullptr; + return raw; +} + +template +Box::Box(uninit) noexcept {} +#endif // CXXBRIDGE1_RUST_BOX + +#ifndef CXXBRIDGE1_RUST_VEC +#define CXXBRIDGE1_RUST_VEC +template +Vec::Vec(std::initializer_list init) : Vec{} { + this->reserve_total(init.size()); + std::move(init.begin(), init.end(), std::back_inserter(*this)); +} + +template +Vec::Vec(const Vec &other) : Vec() { + this->reserve_total(other.size()); + std::copy(other.begin(), other.end(), std::back_inserter(*this)); +} + +template +Vec::Vec(Vec &&other) noexcept : repr(other.repr) { + new (&other) Vec(); +} + +template +Vec::~Vec() noexcept { + this->drop(); +} + +template +Vec &Vec::operator=(Vec &&other) & noexcept { + this->drop(); + this->repr = other.repr; + new (&other) Vec(); + return *this; +} + +template +Vec &Vec::operator=(const Vec &other) & { + if (this != &other) { + this->drop(); + new (this) Vec(other); + } + return *this; +} + +template +bool Vec::empty() const noexcept { + return this->size() == 0; +} + +template +T *Vec::data() noexcept { + return const_cast(const_cast *>(this)->data()); +} + +template +const T &Vec::operator[](std::size_t n) const noexcept { + assert(n < this->size()); + auto data = reinterpret_cast(this->data()); + return *reinterpret_cast(data + n * size_of()); +} + +template +const T &Vec::at(std::size_t n) const { + if (n >= this->size()) { + panic("rust::Vec index out of range"); + } + return (*this)[n]; +} + +template +const T &Vec::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template +const T &Vec::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template +T &Vec::operator[](std::size_t n) noexcept { + assert(n < this->size()); + auto data = reinterpret_cast(this->data()); + return *reinterpret_cast(data + n * size_of()); +} + +template +T &Vec::at(std::size_t n) { + if (n >= this->size()) { + panic("rust::Vec index out of range"); + } + return (*this)[n]; +} + +template +T &Vec::front() noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template +T &Vec::back() noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template +void Vec::reserve(std::size_t new_cap) { + this->reserve_total(new_cap); +} + +template +void Vec::push_back(const T &value) { + this->emplace_back(value); +} + +template +void Vec::push_back(T &&value) { + this->emplace_back(std::move(value)); +} + +template +template +void Vec::emplace_back(Args &&...args) { + auto size = this->size(); + this->reserve_total(size + 1); + ::new (reinterpret_cast(reinterpret_cast(this->data()) + + size * size_of())) + T(std::forward(args)...); + this->set_len(size + 1); +} + +template +void Vec::clear() { + this->truncate(0); +} + +template +typename Vec::iterator Vec::begin() noexcept { + return Slice(this->data(), this->size()).begin(); +} + +template +typename Vec::iterator Vec::end() noexcept { + return Slice(this->data(), this->size()).end(); +} + +template +typename Vec::const_iterator Vec::begin() const noexcept { + return this->cbegin(); +} + +template +typename Vec::const_iterator Vec::end() const noexcept { + return this->cend(); +} + +template +typename Vec::const_iterator Vec::cbegin() const noexcept { + return Slice(this->data(), this->size()).begin(); +} + +template +typename Vec::const_iterator Vec::cend() const noexcept { + return Slice(this->data(), this->size()).end(); +} + +template +void Vec::swap(Vec &rhs) noexcept { + using std::swap; + swap(this->repr, rhs.repr); +} + +// Internal API only intended for the cxxbridge code generator. +template +Vec::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {} +#endif // CXXBRIDGE1_RUST_VEC + +#ifndef CXXBRIDGE1_IS_COMPLETE +#define CXXBRIDGE1_IS_COMPLETE +namespace detail { +namespace { +template +struct is_complete : std::false_type {}; +template +struct is_complete : std::true_type {}; +} // namespace +} // namespace detail +#endif // CXXBRIDGE1_IS_COMPLETE + +#ifndef CXXBRIDGE1_LAYOUT +#define CXXBRIDGE1_LAYOUT +class layout { + template + friend std::size_t size_of(); + template + friend std::size_t align_of(); + template + static typename std::enable_if::value, + std::size_t>::type + do_size_of() { + return T::layout::size(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_size_of() { + return sizeof(T); + } + template + static + typename std::enable_if::value, std::size_t>::type + size_of() { + return do_size_of(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_align_of() { + return T::layout::align(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_align_of() { + return alignof(T); + } + template + static + typename std::enable_if::value, std::size_t>::type + align_of() { + return do_align_of(); + } +}; + +template +std::size_t size_of() { + return layout::size_of(); +} + +template +std::size_t align_of() { + return layout::align_of(); +} +#endif // CXXBRIDGE1_LAYOUT + +#ifndef CXXBRIDGE1_RELOCATABLE +#define CXXBRIDGE1_RELOCATABLE +namespace detail { +template +struct make_void { + using type = void; +}; + +template +using void_t = typename make_void::type; + +template class, typename...> +struct detect : std::false_type {}; +template