├── .envrc ├── .github └── workflows │ └── cicd.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── async_iterator.rs └── complex_lifetimes.rs ├── flake.lock ├── flake.nix ├── rust-toolchain.toml ├── src ├── closure.rs ├── constructor.rs ├── constructor_tests.rs ├── container.rs ├── container_tests.rs ├── function.rs ├── function_tests.rs ├── lib.md ├── lib.rs ├── receiver.rs └── utils.rs └── tests ├── ui.rs └── ui ├── buffered_mut_borrow_twice_fail.rs ├── buffered_mut_borrow_twice_fail.stderr ├── from_closure_coercion_pass.rs ├── from_closure_leak_slot_fail.rs ├── from_closure_leak_slot_fail.stderr ├── from_closure_wrong_type_fail.rs ├── from_closure_wrong_type_fail.stderr ├── from_fn_mut_borrow_arg_twice_fail.rs ├── from_fn_mut_borrow_arg_twice_fail.stderr ├── from_fn_mut_borrow_self_twice_fail.rs ├── from_fn_mut_borrow_self_twice_fail.stderr ├── from_fn_two_diff_mut_ref_pass.rs ├── from_fn_with_closure_fail.rs ├── from_fn_with_closure_fail.stderr ├── from_fn_with_wrong_type_fail.rs ├── from_fn_with_wrong_type_fail.stderr ├── pinned_buffered_unpin_fail.rs └── pinned_buffered_unpin_fail.stderr /.envrc: -------------------------------------------------------------------------------- 1 | use flake . 2 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yml: -------------------------------------------------------------------------------- 1 | # Adapted from: 2 | # - https://github.com/starship/starship/blob/27cc4bc/.github/workflows/release.yml 3 | # - https://github.com/sharkdp/bat/blob/6fc5882/.github/workflows/CICD.yml 4 | name: CICD 5 | on: 6 | push: 7 | branches: [main] 8 | paths-ignore: ["CHANGELOG.md"] 9 | pull_request: 10 | paths-ignore: ["CHANGELOG.md"] 11 | 12 | permissions: 13 | contents: read 14 | 15 | defaults: 16 | run: 17 | shell: bash 18 | 19 | jobs: 20 | metadata: 21 | name: Extract crate metadata 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Setup | Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup | Rust 28 | id: setup-rust 29 | uses: dtolnay/rust-toolchain@stable 30 | - run: rustup override set '${{ steps.setup-rust.outputs.name }}' 31 | 32 | - name: Setup | Extract crate metadata 33 | id: metadata 34 | run: | 35 | cargo metadata --no-deps --format-version=1 \ 36 | | jq -r '.packages[0] 37 | | {name, version, rust_version} 38 | | to_entries 39 | | map("\(.key)=\(.value)") 40 | | join("\n")' \ 41 | | tee -a $GITHUB_OUTPUT 42 | outputs: 43 | name: ${{ steps.metadata.outputs.name }} 44 | version: ${{ steps.metadata.outputs.version }} 45 | rust_version: ${{ steps.metadata.outputs.rust_version }} 46 | 47 | check: 48 | name: Run tests and checks 49 | needs: [metadata] 50 | strategy: 51 | fail-fast: false 52 | matrix: 53 | include: 54 | # prettier-ignore 55 | - { os: ubuntu-latest, toolchain: "${{ needs.metadata.outputs.rust_version }}" } 56 | - { os: ubuntu-latest, toolchain: nightly } 57 | - { os: ubuntu-latest, toolchain: stable } 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - name: Setup | Checkout 61 | uses: actions/checkout@v4 62 | 63 | - name: Setup | Rust 64 | id: setup-rust 65 | uses: dtolnay/rust-toolchain@stable # Avoid frequent cache updates 66 | with: 67 | toolchain: ${{ matrix.toolchain }} 68 | components: ${{ format('clippy,rustfmt{0}', matrix.toolchain == 'nightly' && ',miri' || '') }} 69 | - run: rustup override set '${{ steps.setup-rust.outputs.name }}' # Override rust-toolchain.toml 70 | 71 | - name: Setup | Install cargo-audit 72 | uses: taiki-e/install-action@cargo-audit 73 | 74 | - name: Setup | Install cargo-llvm-cov 75 | if: matrix.toolchain == 'nightly' 76 | uses: taiki-e/install-action@cargo-llvm-cov 77 | 78 | - name: Setup | Rust cache 79 | uses: Swatinem/rust-cache@v2 80 | 81 | - name: Post Setup | Show information 82 | run: | 83 | gcc --version || true 84 | cargo --version 85 | rustc --version 86 | 87 | - name: Check | Audit 88 | run: cargo audit 89 | 90 | - name: Check | Formatting 91 | run: cargo fmt --check 92 | 93 | - name: Check | Clippy 94 | run: cargo clippy --workspace 95 | 96 | - name: Check | Clippy without default features 97 | run: cargo clippy --workspace --no-default-features 98 | 99 | - name: Check | Clippy with all features 100 | run: cargo clippy --workspace --all-features 101 | 102 | - name: Check | Build 103 | run: cargo build --workspace 104 | 105 | - name: Check | Test suite 106 | run: cargo test --workspace --all-features 107 | 108 | - name: Check | Miri 109 | if: matrix.toolchain == 'nightly' 110 | run: cargo miri test --workspace --all-features 111 | 112 | - name: Check | Coverage 113 | if: matrix.toolchain == 'nightly' 114 | run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info 115 | 116 | - name: Check | Rustdoc 117 | if: matrix.toolchain == 'nightly' 118 | run: RUSTDOCFLAGS='--cfg docsrs' cargo doc --all-features --no-deps 119 | 120 | - name: Post Check | Upload coverage to Codecov 121 | if: matrix.toolchain == 'nightly' 122 | uses: codecov/codecov-action@v5 123 | with: 124 | token: ${{ secrets.CODECOV_TOKEN }} 125 | files: lcov.info 126 | fail_ci_if_error: true 127 | 128 | release: 129 | name: Create GitHub release 130 | needs: [metadata, check] 131 | if: startsWith(github.head_ref, 'release/') 132 | permissions: 133 | contents: write # Need to update release 134 | runs-on: ubuntu-latest 135 | steps: 136 | - name: Setup | Checkout 137 | uses: actions/checkout@v4 138 | 139 | # For a PR from "release/v1.0.0", the release tag is set to "v1.0.0" 140 | - name: Setup | Configure 141 | id: configure 142 | run: echo tag="${GITHUB_HEAD_REF#release/}" >$GITHUB_OUTPUT 143 | 144 | # Release notes are taken from the PR's body 145 | - name: Release | Create Release 146 | env: 147 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 148 | release_tag: ${{ steps.configure.outputs.tag }} 149 | release_body: ${{ github.event.pull_request.body }} 150 | run: | 151 | if gh release view "$release_tag" &>/dev/null; then 152 | echo "update existed release $release_tag" 153 | command=edit 154 | else 155 | echo "create new release $release_tag" 156 | command=create 157 | fi 158 | gh release "$command" "$release_tag" \ 159 | --target="$GITHUB_BASE_REF" \ 160 | --draft=true \ 161 | --title="$release_tag ($(date -u +'%Y-%m-%d'))" \ 162 | --notes="$release_body" 163 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | match_block_trailing_comma = true 3 | use_field_init_shorthand = true 4 | # Useful unstable options 5 | unstable_features = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Module" 8 | format_code_in_doc_comments = true 9 | overflow_delimited_expr = true 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project 6 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 32 | 33 | ## [Unreleased] 34 | 35 | ## [0.1.0] - 2025-07-06 36 | 37 | The main purpose of this release is to address unsoundness and introduce breaking changes early to 38 | prevent further issues. Consequently, it includes few changes. 39 | 40 | ### Added 41 | 42 | - Implement `Emplace` for `&mut MaybeUninit<[u8; N]>` ([#2]). 43 | - Implement `Emplace` for `SmallVec` ([#6]). 44 | 45 | ### Changed 46 | 47 | - Add `#[must_use]` for `Slot`, `from_fn!()` and `from_closure()` ([#4]). 48 | 49 | ### Removed 50 | 51 | - (**BREAKING**) Remove `Emplace` implementations for `&mut [u8; N]`, `&mut [u8]` and `&mut Vec` 52 | ([#2]). 53 | 54 | ### Fixed 55 | 56 | - Make the compilation passes when `default-features = false` ([#5]). 57 | 58 | [#6]: https://github.com/loichyan/dynify/pull/6 59 | [#5]: https://github.com/loichyan/dynify/pull/5 60 | [#4]: https://github.com/loichyan/dynify/pull/4 61 | [#2]: https://github.com/loichyan/dynify/pull/2 62 | 63 | ## [0.0.1] - 2025-07-05 64 | 65 | 🎉 Initial release. Check out [README](https://github.com/loichyan/dynify/blob/v0.0.1/README.md) for 66 | more details. 67 | 68 | [Unreleased]: https://github.com/loichyan/dynify/compare/v0.1.0..HEAD 69 | [0.1.0]: https://github.com/loichyan/dynify/releases/tag/v0.1.0 70 | [0.0.1]: https://github.com/loichyan/dynify/releases/tag/v0.0.1 71 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "cfg-if" 16 | version = "1.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 19 | 20 | [[package]] 21 | name = "dynify" 22 | version = "0.1.0" 23 | dependencies = [ 24 | "fastrand", 25 | "pollster", 26 | "rstest", 27 | "smallvec", 28 | "trybuild", 29 | ] 30 | 31 | [[package]] 32 | name = "equivalent" 33 | version = "1.0.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 36 | 37 | [[package]] 38 | name = "fastrand" 39 | version = "2.3.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 42 | 43 | [[package]] 44 | name = "glob" 45 | version = "0.3.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 48 | 49 | [[package]] 50 | name = "hashbrown" 51 | version = "0.15.4" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 54 | 55 | [[package]] 56 | name = "indexmap" 57 | version = "2.10.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 60 | dependencies = [ 61 | "equivalent", 62 | "hashbrown", 63 | ] 64 | 65 | [[package]] 66 | name = "itoa" 67 | version = "1.0.15" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 70 | 71 | [[package]] 72 | name = "memchr" 73 | version = "2.7.5" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 76 | 77 | [[package]] 78 | name = "pollster" 79 | version = "0.4.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 82 | dependencies = [ 83 | "pollster-macro", 84 | ] 85 | 86 | [[package]] 87 | name = "pollster-macro" 88 | version = "0.4.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "ac5da421106a50887c5b51d20806867db377fbb86bacf478ee0500a912e0c113" 91 | dependencies = [ 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "proc-macro2" 99 | version = "1.0.95" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 102 | dependencies = [ 103 | "unicode-ident", 104 | ] 105 | 106 | [[package]] 107 | name = "quote" 108 | version = "1.0.40" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 111 | dependencies = [ 112 | "proc-macro2", 113 | ] 114 | 115 | [[package]] 116 | name = "regex" 117 | version = "1.11.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 120 | dependencies = [ 121 | "aho-corasick", 122 | "memchr", 123 | "regex-automata", 124 | "regex-syntax", 125 | ] 126 | 127 | [[package]] 128 | name = "regex-automata" 129 | version = "0.4.9" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 132 | dependencies = [ 133 | "aho-corasick", 134 | "memchr", 135 | "regex-syntax", 136 | ] 137 | 138 | [[package]] 139 | name = "regex-syntax" 140 | version = "0.8.5" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 143 | 144 | [[package]] 145 | name = "relative-path" 146 | version = "1.9.3" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 149 | 150 | [[package]] 151 | name = "rstest" 152 | version = "0.25.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" 155 | dependencies = [ 156 | "rstest_macros", 157 | "rustc_version", 158 | ] 159 | 160 | [[package]] 161 | name = "rstest_macros" 162 | version = "0.25.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" 165 | dependencies = [ 166 | "cfg-if", 167 | "glob", 168 | "proc-macro2", 169 | "quote", 170 | "regex", 171 | "relative-path", 172 | "rustc_version", 173 | "syn", 174 | "unicode-ident", 175 | ] 176 | 177 | [[package]] 178 | name = "rustc_version" 179 | version = "0.4.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 182 | dependencies = [ 183 | "semver", 184 | ] 185 | 186 | [[package]] 187 | name = "ryu" 188 | version = "1.0.20" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 191 | 192 | [[package]] 193 | name = "semver" 194 | version = "1.0.26" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 197 | 198 | [[package]] 199 | name = "serde" 200 | version = "1.0.219" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 203 | dependencies = [ 204 | "serde_derive", 205 | ] 206 | 207 | [[package]] 208 | name = "serde_derive" 209 | version = "1.0.219" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 212 | dependencies = [ 213 | "proc-macro2", 214 | "quote", 215 | "syn", 216 | ] 217 | 218 | [[package]] 219 | name = "serde_json" 220 | version = "1.0.140" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 223 | dependencies = [ 224 | "itoa", 225 | "memchr", 226 | "ryu", 227 | "serde", 228 | ] 229 | 230 | [[package]] 231 | name = "serde_spanned" 232 | version = "0.6.9" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 235 | dependencies = [ 236 | "serde", 237 | ] 238 | 239 | [[package]] 240 | name = "smallvec" 241 | version = "1.15.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 244 | 245 | [[package]] 246 | name = "syn" 247 | version = "2.0.104" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 250 | dependencies = [ 251 | "proc-macro2", 252 | "quote", 253 | "unicode-ident", 254 | ] 255 | 256 | [[package]] 257 | name = "target-triple" 258 | version = "0.1.4" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" 261 | 262 | [[package]] 263 | name = "termcolor" 264 | version = "1.4.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 267 | dependencies = [ 268 | "winapi-util", 269 | ] 270 | 271 | [[package]] 272 | name = "toml" 273 | version = "0.8.23" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 276 | dependencies = [ 277 | "serde", 278 | "serde_spanned", 279 | "toml_datetime", 280 | "toml_edit", 281 | ] 282 | 283 | [[package]] 284 | name = "toml_datetime" 285 | version = "0.6.11" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 288 | dependencies = [ 289 | "serde", 290 | ] 291 | 292 | [[package]] 293 | name = "toml_edit" 294 | version = "0.22.27" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 297 | dependencies = [ 298 | "indexmap", 299 | "serde", 300 | "serde_spanned", 301 | "toml_datetime", 302 | "toml_write", 303 | "winnow", 304 | ] 305 | 306 | [[package]] 307 | name = "toml_write" 308 | version = "0.1.2" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 311 | 312 | [[package]] 313 | name = "trybuild" 314 | version = "1.0.105" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "1c9bf9513a2f4aeef5fdac8677d7d349c79fdbcc03b9c86da6e9d254f1e43be2" 317 | dependencies = [ 318 | "glob", 319 | "serde", 320 | "serde_derive", 321 | "serde_json", 322 | "target-triple", 323 | "termcolor", 324 | "toml", 325 | ] 326 | 327 | [[package]] 328 | name = "unicode-ident" 329 | version = "1.0.18" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 332 | 333 | [[package]] 334 | name = "winapi-util" 335 | version = "0.1.9" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 338 | dependencies = [ 339 | "windows-sys", 340 | ] 341 | 342 | [[package]] 343 | name = "windows-sys" 344 | version = "0.59.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 347 | dependencies = [ 348 | "windows-targets", 349 | ] 350 | 351 | [[package]] 352 | name = "windows-targets" 353 | version = "0.52.6" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 356 | dependencies = [ 357 | "windows_aarch64_gnullvm", 358 | "windows_aarch64_msvc", 359 | "windows_i686_gnu", 360 | "windows_i686_gnullvm", 361 | "windows_i686_msvc", 362 | "windows_x86_64_gnu", 363 | "windows_x86_64_gnullvm", 364 | "windows_x86_64_msvc", 365 | ] 366 | 367 | [[package]] 368 | name = "windows_aarch64_gnullvm" 369 | version = "0.52.6" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 372 | 373 | [[package]] 374 | name = "windows_aarch64_msvc" 375 | version = "0.52.6" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 378 | 379 | [[package]] 380 | name = "windows_i686_gnu" 381 | version = "0.52.6" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 384 | 385 | [[package]] 386 | name = "windows_i686_gnullvm" 387 | version = "0.52.6" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 390 | 391 | [[package]] 392 | name = "windows_i686_msvc" 393 | version = "0.52.6" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 396 | 397 | [[package]] 398 | name = "windows_x86_64_gnu" 399 | version = "0.52.6" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 402 | 403 | [[package]] 404 | name = "windows_x86_64_gnullvm" 405 | version = "0.52.6" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 408 | 409 | [[package]] 410 | name = "windows_x86_64_msvc" 411 | version = "0.52.6" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 414 | 415 | [[package]] 416 | name = "winnow" 417 | version = "0.7.11" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" 420 | dependencies = [ 421 | "memchr", 422 | ] 423 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynify" 3 | version = "0.1.0" 4 | authors = ["Loi Chyan "] 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | rust-version = "1.80" 8 | 9 | repository = "https://github.com/loichyan/dynify" 10 | description = "Add dyn compatible variant to you async trait" 11 | keywords = ["async", "trait", "impl"] 12 | categories = ["asynchronous", "no-std", "rust-patterns"] 13 | 14 | [features] 15 | default = ["alloc"] 16 | alloc = [] 17 | smallvec = ["dep:smallvec"] 18 | 19 | [dependencies] 20 | smallvec = { version = "1", optional = true } 21 | 22 | [dev-dependencies] 23 | fastrand = "2.3.0" 24 | pollster = { version = "0.4.0", features = ["macro"] } 25 | rstest = { version = "0.25.0", default-features = false } 26 | trybuild = "1.0.105" 27 | 28 | [package.metadata.docs.rs] 29 | all-features = true 30 | rustdoc-args = ["--cfg", "docsrs"] 31 | 32 | [lints.rust] 33 | unexpected_cfgs.level = "warn" 34 | unexpected_cfgs.check-cfg = ["cfg(coverage)", "cfg(coverage_nightly)"] 35 | unknown_lints = "allow" 36 | 37 | [lints.clippy] 38 | uninlined_format_args = "allow" 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🦕 dynify 2 | 3 | [![crates.io](https://img.shields.io/crates/v/dynify)](https://crates.io/crates/dynify) 4 | [![docs.rs](https://img.shields.io/docsrs/dynify)](https://docs.rs/dynify) 5 | [![msrv](https://img.shields.io/crates/msrv/dynify)](https://crates.io/crates/dynify) 6 | [![build status](https://img.shields.io/github/actions/workflow/status/loichyan/dynify/cicd.yml)](https://github.com/loichyan/dynify/actions) 7 | [![codecov](https://img.shields.io/codecov/c/gh/loichyan/dynify)](https://codecov.io/gh/loichyan/dynify) 8 | 9 | Add dyn compatible variant to your async trait with dynify! 10 | 11 | ## ✨ Overview 12 | 13 | dynify implements partial features of the experimental 14 | [in-place initialization proposal](https://github.com/rust-lang/lang-team/issues/336) in stable 15 | Rust, along with a set of safe APIs for creating in-place constructors to initialize trait objects. 16 | Here’s a quick example of how to use dynify: 17 | 18 | ```rust 19 | use dynify::{from_fn, Dynify, Fn}; 20 | use std::future::Future; 21 | use std::mem::MaybeUninit; 22 | 23 | // `AsyncRead` is dyn incompatible :( 24 | trait AsyncRead { 25 | async fn read_to_string(&mut self) -> String; 26 | } 27 | 28 | // With dynify, we can create a dyn compatible variant for `AsyncRead` in a few lines :) 29 | trait DynAsyncRead { 30 | fn read_to_string(&mut self) -> Fn!(&mut Self => dyn '_ + Future); 31 | } 32 | impl DynAsyncRead for T { 33 | fn read_to_string(&mut self) -> Fn!(&mut Self => dyn '_ + Future) { 34 | from_fn!(T::read_to_string, self) 35 | } 36 | } 37 | 38 | // Now we can use dynamic dispatched `AsyncRead`! 39 | async fn dynamic_dispatch(reader: &mut dyn DynAsyncRead) { 40 | let mut stack = [MaybeUninit::::uninit(); 16]; 41 | let mut heap = Vec::>::new(); 42 | // Initialize trait objects on the stack if not too large, otherwise on the heap. 43 | let fut = reader.read_to_string().init2(&mut stack, &mut heap); 44 | let content = fut.await; 45 | // ... 46 | } 47 | ``` 48 | 49 | For a more detailed explanation, check out the [API documentation](https://docs.rs/dynify). 50 | 51 | ## 🔍 Comparisons with other similar projects 52 | 53 | ### vs pin-init 54 | 55 | [pin-init](https://crates.io/crates/pin-init) has been around for a while and provides safe methods 56 | for creating in-place constructors for `struct`s. It also has an 57 | [experimental branch](https://github.com/Rust-for-Linux/pin-init/tree/dev/experimental/dyn) that 58 | enables the generation of dyn compatible variants for `async fn`s. The key difference is that 59 | pin-init relies on some nightly features, while dynify is built with stable Rust. Moreover, as their 60 | names suggest, pin-init is focused on the pinned initialization of structures, whereas dynify 61 | targets dyn compatibility for functions. With its ongoing `#[dyn_init]` feature, pin-init can be 62 | considered as a superset of dynify. 63 | 64 | ### vs async-trait 65 | 66 | [async-trait](https://crates.io/crates/async-trait) is another widely used crate for dynamic 67 | dispatch on AFIT (Async Fn In Trait). The main advantage of dynify is its ability to allocate trait 68 | objects on the stack, making it more suitable for limited environments. In contrast, async-trait 69 | requires heap allocation to store trait objects, as it essentially transforms `async fn` into 70 | `Box`. 71 | 72 | ## ♥️ Special thanks 73 | 74 | - [Rust-for-Linux/pin-init](https://github.com/Rust-for-Linux/pin-init) for its brilliant design on 75 | creating constructors for `async fn`s, which serves as the foundation of dynify. 76 | - [In-place initialization proposal](https://hackmd.io/@aliceryhl/BJutRcPblx) for its excellent 77 | design on initializer traits, which is incorporated into several trait designs of dynify. 78 | - [zjp-CN/dyn-afit](https://github.com/zjp-CN/dyn-afit) for the comprehensive comparisons of 79 | community solutions for dynamic dispatch on AFIT, which greatly inspired dynify. 80 | 81 | ## ⚖️ License 82 | 83 | Licensed under either of 84 | 85 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 86 | ) 87 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 88 | 89 | at your option. 90 | -------------------------------------------------------------------------------- /examples/async_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::mem::MaybeUninit; 3 | 4 | use dynify::{from_fn, Dynify, Fn}; 5 | 6 | trait Stream { 7 | type Item; 8 | async fn next(&mut self) -> Option; 9 | } 10 | 11 | trait DynStream { 12 | type Item; 13 | fn next(&mut self) -> Fn!(&'_ mut Self => dyn '_ + Future>); 14 | } 15 | impl DynStream for T { 16 | type Item = T::Item; 17 | fn next(&mut self) -> Fn!(&'_ mut Self => dyn '_ + Future>) { 18 | from_fn!(T::next, self) 19 | } 20 | } 21 | 22 | struct FromIter(I); 23 | impl Stream for FromIter 24 | where 25 | I: Iterator, 26 | { 27 | type Item = I::Item; 28 | async fn next(&mut self) -> Option { 29 | self.0.next() 30 | } 31 | } 32 | 33 | /// Validates the input with dynamic dispatched streams! 34 | async fn validate_data(data: &str, iter: &mut dyn DynStream) { 35 | let mut stack = [MaybeUninit::::uninit(); 16]; 36 | let mut heap = Vec::>::new(); 37 | let mut data_iter = data.chars(); 38 | 39 | while let Some(ch) = iter.next().init2(&mut stack, &mut heap).await { 40 | let expected = data_iter.next().unwrap(); 41 | println!("> yielded={}, expected={}", ch, expected); 42 | assert_eq!(ch, expected); 43 | } 44 | assert_eq!(data_iter.count(), 0); 45 | } 46 | 47 | #[pollster::main] 48 | async fn main() { 49 | let data = std::iter::repeat_with(fastrand::alphanumeric) 50 | .take(fastrand::usize(16..32)) 51 | .collect::(); 52 | println!("generated data: {}", data); 53 | 54 | let mut iter = FromIter(data.chars()); 55 | validate_data(&data, &mut iter).await; 56 | println!("finished data validation"); 57 | } 58 | -------------------------------------------------------------------------------- /examples/complex_lifetimes.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::mem::MaybeUninit; 3 | 4 | use dynify::{from_fn, Dynify, Fn}; 5 | 6 | trait UserCommunication { 7 | async fn send_sms(&self, phone: &str, code: &str); 8 | async fn send_email(&self, email: &str, code: &str); 9 | } 10 | 11 | // TODO: implement a proc_macro for such boilerplates? 12 | trait DynUserCommunication { 13 | fn send_sms<'this, 'phone, 'code, 'ret>( 14 | &'this self, 15 | phone: &'phone str, 16 | code: &'code str, 17 | ) -> Fn!(&'this Self, &'phone str, &'code str => dyn 'ret + Future) 18 | where 19 | 'this: 'ret, 20 | 'phone: 'ret, 21 | 'code: 'ret; 22 | 23 | fn send_email<'this, 'email, 'code, 'ret>( 24 | &'this self, 25 | email: &'email str, 26 | code: &'code str, 27 | ) -> Fn!(&'this Self, &'email str, &'code str => dyn 'ret + Future) 28 | where 29 | 'this: 'ret, 30 | 'email: 'ret, 31 | 'code: 'ret; 32 | } 33 | impl DynUserCommunication for T { 34 | fn send_sms<'this, 'phone, 'code, 'ret>( 35 | &'this self, 36 | phone: &'phone str, 37 | code: &'code str, 38 | ) -> Fn!(&'this Self, &'phone str, &'code str => dyn 'ret + Future) 39 | where 40 | 'this: 'ret, 41 | 'phone: 'ret, 42 | 'code: 'ret, 43 | { 44 | from_fn!(T::send_sms, self, phone, code) 45 | } 46 | 47 | fn send_email<'this, 'email, 'code, 'ret>( 48 | &'this self, 49 | email: &'email str, 50 | code: &'code str, 51 | ) -> Fn!(&'this Self, &'email str, &'code str => dyn 'ret + Future) 52 | where 53 | 'this: 'ret, 54 | 'email: 'ret, 55 | 'code: 'ret, 56 | { 57 | from_fn!(T::send_email, self, email, code) 58 | } 59 | } 60 | 61 | struct TestUser<'a>(&'a str); 62 | impl UserCommunication for TestUser<'_> { 63 | async fn send_sms(&self, phone: &str, code: &str) { 64 | println!( 65 | "send sms to user({}), phone={}, code={}", 66 | self.0, phone, code 67 | ); 68 | } 69 | async fn send_email(&self, email: &str, code: &str) { 70 | println!( 71 | "send email to user({}), email={}, code={}", 72 | self.0, email, code 73 | ); 74 | } 75 | } 76 | 77 | async fn dynamic_dispatch(conn: &dyn DynUserCommunication) { 78 | let mut stack = MaybeUninit::<[u8; 16]>::uninit(); 79 | let mut heap = Vec::>::new(); 80 | conn.send_sms("123-456-789", "7519") 81 | .init2(&mut stack, &mut heap) 82 | .await; 83 | conn.send_email("pink@rock.star", "1509") 84 | .init2(&mut stack, &mut heap) 85 | .await; 86 | } 87 | 88 | #[pollster::main] 89 | async fn main() { 90 | let user = TestUser("rolling_fancy_2557"); 91 | dynamic_dispatch(&user).await; 92 | } 93 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1748662220, 24 | "narHash": "sha256-7gGa49iB9nCnFk4h/g9zwjlQAyjtpgcFkODjcOQS0Es=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "59138c7667b7970d205d6a05a8bfa2d78caa3643", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixpkgs-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs", 41 | "rust-overlay": "rust-overlay" 42 | } 43 | }, 44 | "rust-overlay": { 45 | "inputs": { 46 | "nixpkgs": [ 47 | "nixpkgs" 48 | ] 49 | }, 50 | "locked": { 51 | "lastModified": 1748658947, 52 | "narHash": "sha256-F+nGITu6D7RswJlm8qCuU1PCuOSgDeAqaDKWW1n1jmQ=", 53 | "owner": "oxalica", 54 | "repo": "rust-overlay", 55 | "rev": "fc82ce758cc5df6a6d5d24e75710321cdbdc787a", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "oxalica", 60 | "repo": "rust-overlay", 61 | "type": "github" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | rust-overlay = { 6 | url = "github:oxalica/rust-overlay"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | 11 | outputs = 12 | { 13 | self, 14 | nixpkgs, 15 | flake-utils, 16 | rust-overlay, 17 | }: 18 | flake-utils.lib.eachDefaultSystem ( 19 | system: 20 | let 21 | pkgs = import nixpkgs { 22 | inherit system; 23 | overlays = [ rust-overlay.overlays.default ]; 24 | }; 25 | inherit (pkgs) lib mkShellNoCC rust-bin; 26 | 27 | rustupToolchain = (lib.importTOML ./rust-toolchain.toml).toolchain; 28 | crateMetadata = (lib.importTOML ./Cargo.toml).package; 29 | 30 | # Rust toolchain for development 31 | rust-dev = rust-bin.fromRustupToolchain rustupToolchain; 32 | rust-dev-with-rust-analyzer = rust-dev.override (prev: { 33 | extensions = prev.extensions ++ [ 34 | "rust-src" 35 | "rust-analyzer" 36 | ]; 37 | }); 38 | 39 | # Rust toolchain of MSRV 40 | rust-msrv = rust-bin.fromRustupToolchain { 41 | channel = crateMetadata.rust-version; 42 | profile = "minimal"; 43 | }; 44 | 45 | mkDevShell = 46 | devPkgs: 47 | (mkShellNoCC { 48 | packages = 49 | with pkgs; 50 | [ 51 | # Necessary packages for build 52 | openssl 53 | cargo-binutils 54 | ] 55 | ++ devPkgs; 56 | }); 57 | in 58 | { 59 | packages.default = pkgs.rustPlatform.buildRustPackage { 60 | pname = crateMetadata.name; 61 | version = crateMetadata.version; 62 | src = ./.; 63 | cargoLock.lockFile = ./Cargo.lock; 64 | meta = { 65 | description = crateMetadata.description; 66 | homepage = crateMetadata.repository; 67 | license = with lib.licenses; [ 68 | mit 69 | asl20 70 | ]; 71 | }; 72 | }; 73 | 74 | # The default devShell with IDE integrations 75 | devShells.default = mkDevShell [ rust-dev-with-rust-analyzer ]; 76 | # A minimal devShell without IDE integrations 77 | devShells.minimal = mkDevShell [ rust-dev ]; 78 | # A minimal devShell with toolchain of MSRV 79 | devShells.msrv = mkDevShell [ rust-msrv ]; 80 | } 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.80" 3 | -------------------------------------------------------------------------------- /src/closure.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::Layout; 2 | use core::marker::PhantomData; 3 | use core::ptr::NonNull; 4 | 5 | use crate::constructor::{Construct, Opaque, PinConstruct, Slot}; 6 | 7 | /// The constructor created by [`from_closure`]. 8 | #[must_use = "constructor must be initialized"] 9 | pub struct Closure(F, PhantomData); 10 | // SAFETY: 11 | // - A typed slot only accepts writes of objects of type `T`, ensuring that the 12 | // layout of the target object always matches `layout()`. 13 | // - Due to the Higher-Rank Trait Bounds of `F`, references to slots passed to 14 | // it cannot be moved out, making it impossible to return a slot from an 15 | // arbitrary address in safe Rust. 16 | // - Once a slot is consumed, it returns an opaque reference to the filled 17 | // object, which cannot be projected, thus guaranteeing that the layout of its 18 | // pointee always matches `T`. 19 | unsafe impl PinConstruct for Closure 20 | where 21 | U: ?Sized, 22 | F: FnOnce(Slot) -> &mut Opaque, 23 | { 24 | type Object = U; 25 | fn layout(&self) -> Layout { 26 | Layout::new::() 27 | } 28 | unsafe fn construct(self, slot: Slot) -> NonNull { 29 | let ptr = (self.0)(slot.cast()); 30 | NonNull::from(ptr.as_mut()) 31 | } 32 | } 33 | unsafe impl Construct for Closure 34 | where 35 | U: ?Sized, 36 | F: FnOnce(Slot) -> &mut Opaque, 37 | { 38 | } 39 | 40 | /// Creates a new closure constructor. 41 | /// 42 | /// It accepts a closure `f` that writes an object of type `T` to the provided 43 | /// slot. When the returned instance is ready to be [`construct`]ed, `f` gets 44 | /// invoked, and its return value is then used as the object pointer. The type 45 | /// of the pointee for the returned reference may differ from `T`. In other 46 | /// words, the actual object type of the returned constructor is `U`, which is 47 | /// not necessarily the same as `T`. 48 | /// 49 | /// # Example 50 | /// 51 | /// ```rust 52 | /// # use dynify::{from_closure, Opaque, PinDynify}; 53 | /// # use std::future::Future; 54 | /// # pollster::block_on(async { 55 | /// let fut = async { String::from("(o.O)") }; 56 | /// let kmoji = from_closure(|slot| { 57 | /// // The initialized object is selaed in `Opaque`, 58 | /// let init: &mut Opaque<_> = slot.write(fut); 59 | /// // but it doesn't prevent us from coercing it into a trait object. 60 | /// init as &mut Opaque> 61 | /// }); 62 | /// assert_eq!(kmoji.pin_boxed().await, "(o.O)"); 63 | /// # }); 64 | /// ``` 65 | /// 66 | /// [`construct`]: PinConstruct::construct 67 | #[inline(always)] 68 | pub fn from_closure(f: F) -> Closure 69 | where 70 | U: ?Sized, 71 | F: FnOnce(Slot) -> &mut Opaque, 72 | { 73 | Closure(f, PhantomData) 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | use crate::utils::{randstr, OpqStrFut}; 80 | use crate::PinDynify; 81 | 82 | #[pollster::test] 83 | async fn from_closure_works() { 84 | let inp = randstr(8..64); 85 | let init = from_closure(|slot| slot.write(async { inp.clone() }) as &mut OpqStrFut); 86 | assert_eq!(init.pin_boxed().await, inp); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/constructor.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::Layout; 2 | use core::fmt; 3 | use core::marker::PhantomData; 4 | use core::pin::Pin; 5 | use core::ptr::NonNull; 6 | 7 | use crate::container::{Emplace, PinEmplace}; 8 | use crate::utils::Void; 9 | 10 | /// The core trait to package necessary information for object constructions. 11 | /// 12 | /// A type that implements [`Construct`] is called a *constructor*. The value to 13 | /// be constructed in the target memory location is called an *object*, of which 14 | /// type is specified as [`Object`]. 15 | /// 16 | /// # Examples 17 | /// 18 | /// ```rust 19 | /// # use dynify::{Construct, PinConstruct, Slot}; 20 | /// # use std::alloc::Layout; 21 | /// # use std::any::Any; 22 | /// # use std::ptr::NonNull; 23 | /// struct I32Construct(fn() -> i32); 24 | /// unsafe impl PinConstruct for I32Construct { 25 | /// type Object = dyn Any; 26 | /// fn layout(&self) -> Layout { 27 | /// Layout::new::() 28 | /// } 29 | /// unsafe fn construct(self, slot: Slot) -> NonNull { 30 | /// slot.write_unchecked((self.0)()) as NonNull<_> 31 | /// } 32 | /// } 33 | /// unsafe impl Construct for I32Construct {} 34 | /// ``` 35 | /// 36 | /// # Safety 37 | /// 38 | /// For the implementor, 39 | /// 40 | /// - It must adhere to the documented contracts of each method. 41 | /// - The object placed in the provided slot in [`construct`] must have the same 42 | /// layout as that returned from [`layout`]. 43 | /// 44 | /// [`Object`]: Self::Object 45 | /// [`construct`]: Self::construct 46 | /// [`layout`]: Self::layout 47 | pub unsafe trait PinConstruct: Sized { 48 | /// The type of objects to be constructed. 49 | type Object: ?Sized; 50 | 51 | /// Returns the layout of the object to be constructed. 52 | /// 53 | /// [`Object`] can be a sized or unsize type. In the former case, this 54 | /// returns its layout. While in the latter case, this returns the layout of 55 | /// the original type of the coerced DST. 56 | /// 57 | /// [`Object`]: Self::Object 58 | fn layout(&self) -> Layout; 59 | 60 | /// Constructs the object in the specified address. 61 | /// 62 | /// This function will write the object to `slot` and therefore overwrite 63 | /// existing data at the address of `slot`. For the returned pointer, the 64 | /// following statements are always true: 65 | /// 66 | /// - It has the same address as the memory block owned by `slot`. 67 | /// - It may contain additional metadata if [`Object`] is unsized. 68 | /// - Invoking [`Layout::for_value`] with the deference of it returns a 69 | /// layout that matches the one from [`Self::layout`] exactly. 70 | /// 71 | /// # Safety 72 | /// 73 | /// The memory block owned by `slot` must meet the following requirements: 74 | /// 75 | /// - It satisfies the size and alignment constraints of the layout returned 76 | /// from [`Self::layout`]. 77 | /// - It must be exclusive for this construction. 78 | /// 79 | /// Furthermore, if `Self` does not implement [`Construct`], the caller 80 | /// must ensure the pinning requirements are upheld for the returned 81 | /// pointer. 82 | /// 83 | /// [`Object`]: Self::Object 84 | unsafe fn construct(self, slot: Slot) -> NonNull; 85 | } 86 | 87 | /// A marker for constructors that do not require pinned containers. 88 | /// 89 | /// # Safety 90 | /// 91 | /// See the safety notes of [`PinConstruct`]. Additionally, the implementor must 92 | /// ensure that the implementation of [`construct`] does not rely on a pinned 93 | /// memory block. 94 | /// 95 | /// [`construct`]: PinConstruct::construct 96 | pub unsafe trait Construct: PinConstruct {} 97 | 98 | unsafe impl PinConstruct for &'_ mut Option { 99 | type Object = T::Object; 100 | fn layout(&self) -> Layout { 101 | self.as_ref() 102 | .expect("constructor has been consumed") 103 | .layout() 104 | } 105 | unsafe fn construct(self, slot: Slot) -> NonNull { 106 | self.take() 107 | .expect("constructor has been consumed") 108 | .construct(slot) 109 | } 110 | } 111 | unsafe impl Construct for &'_ mut Option {} 112 | 113 | /// A memory block used to store arbitrary objects. 114 | #[must_use = "slot must be consumed"] 115 | pub struct Slot<'a, T: ?Sized = Void>(NonNull, PhantomData<&'a mut T>); 116 | impl<'a> Slot<'a> { 117 | /// Creates a new slot from the supplied pointer. 118 | /// 119 | /// # Safety 120 | /// 121 | /// - The returned instance may not be used outside of [`construct`]. 122 | /// - [`Construct`]s will write objects directly to the address of `ptr`, 123 | /// hence `ptr` must meet all safety requirements of [`construct`]. 124 | /// 125 | /// [`construct`]: PinConstruct::construct 126 | pub unsafe fn new_unchecked(ptr: NonNull) -> Self { 127 | Self(ptr.cast(), PhantomData) 128 | } 129 | 130 | /// Consumes this slot, filling it with the supplied object. 131 | /// 132 | /// # Safety 133 | /// 134 | /// The object may not have a different layout than the one returned from 135 | /// [`PinConstruct::layout`]. 136 | pub unsafe fn write_unchecked(self, object: T) -> NonNull { 137 | let ptr = self.0.cast::(); 138 | debug_assert!(ptr.is_aligned()); 139 | ptr.write(object); 140 | ptr 141 | } 142 | 143 | /// Transforms this slot into a typed one. 144 | /// 145 | /// # Safety 146 | /// 147 | /// The layout of `T` must match that from [`PinConstruct::layout`] exactly. 148 | pub unsafe fn cast(self) -> Slot<'a, T> { 149 | Slot(self.0.cast(), PhantomData) 150 | } 151 | } 152 | impl<'a, T> Slot<'a, T> { 153 | /// Consumes this slot, filling it with the supplied object. 154 | /// 155 | /// The returned object is sealed in [`Opaque`] to prevent misuse. Despite 156 | /// this, as demonstrated below, it's still possible to coerce it into a 157 | /// trait object: 158 | /// 159 | /// ```rust 160 | /// # use dynify::{Opaque, Slot}; 161 | /// # use std::any::Any; 162 | /// fn fill_slot(slot: Slot) -> &mut Opaque { 163 | /// slot.write(String::from("WRYYY!")) as &mut Opaque 164 | /// } 165 | /// ``` 166 | pub fn write(self, object: T) -> &'a mut Opaque { 167 | unsafe { 168 | Slot::new_unchecked(self.into_raw()) 169 | .write_unchecked(Opaque(object)) 170 | .as_mut() 171 | } 172 | } 173 | } 174 | impl<'a, T: ?Sized> Slot<'a, T> { 175 | /// Consumes this instance, returning a raw pointer to the allocated memory 176 | /// block. 177 | pub fn into_raw(self) -> NonNull { 178 | self.0.cast() 179 | } 180 | 181 | /// Returns a raw pointer to the allocated memory block. 182 | pub(crate) fn as_ptr(&self) -> NonNull { 183 | self.0.cast() 184 | } 185 | } 186 | 187 | impl fmt::Debug for Slot<'_, T> { 188 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 189 | self.0.fmt(f) 190 | } 191 | } 192 | 193 | /// A opaque wrapper of initialized objects. 194 | /// 195 | /// This structure has the same in-memory representation as its inner type `T`. 196 | /// Therefore, it is possible, although highly discouraged, to [`transmute`] 197 | /// between `Opaque` and `T`. 198 | /// 199 | /// [`transmute`]: core::mem::transmute 200 | #[repr(transparent)] 201 | pub struct Opaque(T); 202 | impl Opaque { 203 | pub(crate) fn as_mut(&mut self) -> &mut T { 204 | &mut self.0 205 | } 206 | } 207 | 208 | /// The main interface used to perform in-place object constructions. 209 | pub trait Dynify: Construct { 210 | /// Constructs the object in the supplied container. 211 | /// 212 | /// For non-panicking variant, use [`try_init`](Self::try_init). 213 | /// 214 | /// # Panic 215 | /// 216 | /// It panics if `container` fails to construct the object. 217 | fn init(self, container: C) -> C::Ptr 218 | where 219 | C: Emplace, 220 | { 221 | self.try_init(container) 222 | .unwrap_or_else(|_| panic!("failed to initialize")) 223 | } 224 | 225 | /// Constructs the object in the supplied container. 226 | /// 227 | /// If the construction succeeds, it returns the pointer to the object. 228 | /// Otherwise, `self` is returned along with the encountered error. 229 | fn try_init(self, container: C) -> Result 230 | where 231 | C: Emplace, 232 | { 233 | let mut fallible = FallibleConstructor::new(self); 234 | // SAFETY: `fallible` is dropped immediately after it gets consumed. 235 | let handle = unsafe { fallible.handle() }; 236 | match container.emplace(handle) { 237 | Ok(p) => { 238 | debug_assert!(fallible.consumed()); 239 | core::mem::forget(fallible); 240 | Ok(p) 241 | }, 242 | Err(e) => Err((fallible.into_inner(), e)), 243 | } 244 | } 245 | 246 | /// Constructs the object in two containers in turn. 247 | /// 248 | /// For non-panicking variant, use [`try_init2`](Self::try_init2). 249 | /// 250 | /// # Examples 251 | /// 252 | /// ```rust 253 | /// # use dynify::{from_fn, Dynify, Fn}; 254 | /// # use std::future::Future; 255 | /// # use std::mem::MaybeUninit; 256 | /// # pollster::block_on(async { 257 | /// let mut stack = MaybeUninit::<[u8; 16]>::uninit(); 258 | /// let mut heap = Vec::>::new(); 259 | /// 260 | /// let constructor: Fn!(=> dyn Future) = from_fn!(|| async { 777 }); 261 | /// let ret = constructor.init2(&mut stack, &mut heap).await; 262 | /// assert_eq!(ret, 777); 263 | /// # }); 264 | /// ``` 265 | /// 266 | /// # Panic 267 | /// 268 | /// It panics if both containers fail to construct the object. 269 | fn init2(self, container1: C1, container2: C2) -> P 270 | where 271 | C1: Emplace, 272 | C2: Emplace, 273 | { 274 | self.try_init2(container1, container2) 275 | .unwrap_or_else(|_| panic!("failed to initialize")) 276 | } 277 | 278 | /// Constructs the object in two containers in turn. 279 | /// 280 | /// It returns the object pointer if either container succeeds. Otherwise, 281 | /// it forwards the error returned from `container2`. 282 | fn try_init2(self, container1: C1, container2: C2) -> Result 283 | where 284 | C1: Emplace, 285 | C2: Emplace, 286 | { 287 | self.try_init(container1) 288 | .or_else(|(this, _)| this.try_init(container2)) 289 | } 290 | 291 | /// Constructs the object in [`Box`](alloc::boxed::Box). 292 | /// 293 | /// This function never fails as long as there is enough free memory. 294 | #[cfg(feature = "alloc")] 295 | fn boxed(self) -> alloc::boxed::Box { 296 | self.init(crate::container::Boxed) 297 | } 298 | } 299 | impl Dynify for T {} 300 | 301 | /// A variant of [`Dynify`] that requires pinned containers. 302 | pub trait PinDynify: PinConstruct { 303 | /// Constructs the object in the supplied container. 304 | /// 305 | /// For non-panicking variant, use [`try_pin_init`](Self::try_pin_init). 306 | /// 307 | /// # Panic 308 | /// 309 | /// It panics if `container` fails to construct the object. 310 | fn pin_init(self, container: C) -> Pin 311 | where 312 | C: PinEmplace, 313 | { 314 | self.try_pin_init(container) 315 | .unwrap_or_else(|_| panic!("failed to initialize")) 316 | } 317 | 318 | /// Constructs the object in the supplied container. 319 | /// 320 | /// If the construction succeeds, it returns the pointer to the object. 321 | /// Otherwise, `self` is returned along with the encountered error. 322 | fn try_pin_init(self, container: C) -> Result, (Self, C::Err)> 323 | where 324 | C: PinEmplace, 325 | { 326 | let mut fallible = FallibleConstructor::new(self); 327 | // SAFETY: `fallible` is dropped immediately after it gets consumed. 328 | let handle = unsafe { fallible.handle() }; 329 | match container.pin_emplace(handle) { 330 | Ok(p) => { 331 | debug_assert!(fallible.consumed()); 332 | core::mem::forget(fallible); 333 | Ok(p) 334 | }, 335 | Err(e) => Err((fallible.into_inner(), e)), 336 | } 337 | } 338 | 339 | /// Constructs the object in two containers in turn. 340 | /// 341 | /// For non-panicking variant, use [`try_pin_init2`](Self::try_pin_init2). 342 | /// 343 | /// # Panic 344 | /// 345 | /// It panics if both containers fail to construct the object. 346 | fn pin_init2(self, container1: C1, container2: C2) -> Pin

347 | where 348 | C1: PinEmplace, 349 | C2: PinEmplace, 350 | { 351 | self.try_pin_init2(container1, container2) 352 | .unwrap_or_else(|_| panic!("failed to initialize")) 353 | } 354 | 355 | /// Constructs the object in two containers in turn. 356 | /// 357 | /// It returns the object pointer if either container succeeds. Otherwise, 358 | /// it forwards the error returned from `container2`. 359 | fn try_pin_init2( 360 | self, 361 | container1: C1, 362 | container2: C2, 363 | ) -> Result, (Self, C2::Err)> 364 | where 365 | C1: PinEmplace, 366 | C2: PinEmplace, 367 | { 368 | self.try_pin_init(container1) 369 | .or_else(|(this, _)| this.try_pin_init(container2)) 370 | } 371 | 372 | /// Constructs the object in [`Box`](alloc::boxed::Box). 373 | /// 374 | /// This function never fails as long as there is enough free memory. 375 | /// 376 | /// # Examples 377 | /// 378 | /// ```rust 379 | /// # use dynify::{from_fn, Fn, PinDynify}; 380 | /// # use std::any::Any; 381 | /// # use std::pin::Pin; 382 | /// let constructor: Fn!(=> dyn Any) = from_fn!(|| 123); 383 | /// let _: Pin> = constructor.pin_boxed(); 384 | /// ``` 385 | #[cfg(feature = "alloc")] 386 | fn pin_boxed(self) -> Pin> { 387 | self.pin_init(crate::container::Boxed) 388 | } 389 | } 390 | impl PinDynify for T {} 391 | 392 | /// A utility type to reuse the inner constructor if construction fails. 393 | struct FallibleConstructor(Option); 394 | impl FallibleConstructor { 395 | /// Wraps the supplied constructor and returns a new instance. 396 | pub fn new(constructor: T) -> Self { 397 | Self(Some(constructor)) 398 | } 399 | 400 | /// Returns whether the inner constructor is consumed. 401 | pub fn consumed(&self) -> bool { 402 | self.0.is_none() 403 | } 404 | 405 | /// Consumes this instance, returning the inner constructor. 406 | pub fn into_inner(self) -> T { 407 | debug_assert!(!self.consumed()); 408 | unwrap_unchecked(self.0) 409 | } 410 | 411 | /// Returns a handle for fallible construction. 412 | /// 413 | /// # Safety 414 | /// 415 | /// For the returned handle: 416 | /// 417 | /// - It may not be used in non-pinned containers if the underlying 418 | /// constructor requires pinned memory blocks. 419 | /// - After it is [`construct`]ed, `self` must either be [`drop`]ed or 420 | /// [`forget`]ed immediately. Future access will lead to *undefined 421 | /// behavior*. 422 | /// 423 | /// [`construct`]: PinConstruct::construct 424 | /// [`forget`]: core::mem::forget 425 | pub unsafe fn handle(&mut self) -> FallibleHandle<'_, T> { 426 | debug_assert!(!self.consumed()); 427 | FallibleHandle(&mut self.0) 428 | } 429 | } 430 | 431 | /// The handle to perform fallible constructions. 432 | /// 433 | /// If it is not consumed through [`construct`], the inner constructor remains 434 | /// valid. Otherwise, the inner value gets taken, leading to *undefined 435 | /// behavior* for future access. 436 | /// 437 | /// [`construct`]: PinConstruct::construct 438 | struct FallibleHandle<'a, T>(&'a mut Option); 439 | unsafe impl PinConstruct for FallibleHandle<'_, T> { 440 | type Object = T::Object; 441 | fn layout(&self) -> Layout { 442 | unwrap_unchecked(self.0.as_ref()).layout() 443 | } 444 | unsafe fn construct(self, slot: Slot) -> NonNull { 445 | unwrap_unchecked(self.0.take()).construct(slot) 446 | } 447 | } 448 | unsafe impl Construct for FallibleHandle<'_, T> {} 449 | 450 | fn unwrap_unchecked(opt: Option) -> U { 451 | match opt { 452 | Some(t) => t, 453 | // SAFETY: The validity of the constructor inside `Option` is guaranteed 454 | // by the caller. 455 | None => unsafe { core::hint::unreachable_unchecked() }, 456 | } 457 | } 458 | 459 | #[cfg(test)] 460 | #[cfg_attr(coverage_nightly, coverage(off))] 461 | #[path = "constructor_tests.rs"] 462 | mod tests; 463 | -------------------------------------------------------------------------------- /src/constructor_tests.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use rstest::rstest; 4 | 5 | use crate::utils::*; 6 | use crate::{from_closure, Dynify, Emplace, PinDynify}; 7 | 8 | struct UnsafePinnedContainer(C); 9 | unsafe impl Emplace for UnsafePinnedContainer 10 | where 11 | T: ?Sized, 12 | D: Emplace, 13 | { 14 | type Ptr = D::Ptr; 15 | type Err = D::Err; 16 | fn emplace(self, constructor: C) -> Result 17 | where 18 | C: crate::Construct, 19 | { 20 | self.0.emplace(constructor) 21 | } 22 | } 23 | // SAFETY: For testing purpose only, use it carefully. 24 | unsafe impl crate::PinEmplace for UnsafePinnedContainer 25 | where 26 | T: ?Sized, 27 | C: Emplace, 28 | { 29 | } 30 | impl UnsafePinnedContainer<&'_ mut C> { 31 | fn as_mut(&mut self) -> UnsafePinnedContainer<&mut C> { 32 | UnsafePinnedContainer(self.0) 33 | } 34 | } 35 | 36 | #[rstest] 37 | #[case(0, randarr::<0>())] 38 | #[case(1, randarr::<0>())] 39 | #[case(4, randarr::<4>())] 40 | #[case(7, randarr::<5>())] 41 | fn init_ok(#[case] stk_size: usize, #[case] data: [u8; N]) { 42 | let mut stk = newheap_fixed(stk_size); 43 | 44 | let init = from_closure(|slot| slot.write(data) as &mut OpqAny); 45 | let out = init.init(&mut *stk); 46 | assert_eq!(out.downcast_ref::<[u8; N]>(), Some(&data)); 47 | drop(out); 48 | 49 | let mut stk = UnsafePinnedContainer(&mut *stk); 50 | 51 | let init = from_closure(|slot| slot.write(data) as &mut OpqAny); 52 | let out = init.pin_init(stk.as_mut()); 53 | assert_eq!(out.downcast_ref::<[u8; N]>(), Some(&data)); 54 | } 55 | 56 | #[rstest] 57 | #[case(0, 4, randarr::<4>())] 58 | #[case(4, 5, randarr::<5>())] 59 | #[case(0, 8, randarr::<6>())] 60 | #[case(6, 9, randarr::<7>())] 61 | fn init2_ok( 62 | #[case] stk1_size: usize, 63 | #[case] stk2_size: usize, 64 | #[case] data: [u8; N], 65 | ) { 66 | let mut stk1 = newheap_fixed(stk1_size); 67 | let mut stk2 = newheap_fixed(stk2_size); 68 | 69 | let mut init = from_closure(|slot| slot.write(data) as &mut OpqAny); 70 | (init, _) = init.try_init(&mut *stk1).unwrap_err(); 71 | let out = init.init2(&mut *stk1, &mut *stk2); 72 | assert_eq!(out.downcast_ref::<[u8; N]>(), Some(&data)); 73 | drop(out); 74 | 75 | let mut stk1 = UnsafePinnedContainer(&mut *stk1); 76 | let mut stk2 = UnsafePinnedContainer(&mut *stk2); 77 | 78 | let mut init = from_closure(|slot| slot.write(data) as &mut OpqAny); 79 | (init, _) = init.try_pin_init(stk1.as_mut()).unwrap_err(); 80 | let out = init.pin_init2(stk1.as_mut(), stk2.as_mut()); 81 | assert_eq!(out.downcast_ref::<[u8; N]>(), Some(&data)); 82 | } 83 | 84 | #[rstest] 85 | #[case(0, 0, randarr::<7>())] 86 | #[case(6, 0, randarr::<7>())] 87 | #[case(0, 8, randarr::<9>())] 88 | #[case(7, 8, randarr::<9>())] 89 | #[should_panic = "failed to initialize"] 90 | fn panic_on_init_fail( 91 | #[case] stk1_size: usize, 92 | #[case] stk2_size: usize, 93 | #[case] val: impl DebugAny, 94 | ) { 95 | let mut stk1 = newheap_fixed(stk1_size); 96 | let mut stk2 = newheap_fixed(stk2_size); 97 | 98 | let init = from_closure(|slot| slot.write(val)); 99 | if stk2_size == 0 { 100 | init.init(&mut *stk1); 101 | } else { 102 | init.init2(&mut *stk1, &mut *stk2); 103 | } 104 | } 105 | 106 | #[rstest] 107 | #[case(0, 0, randarr::<7>())] 108 | #[case(6, 0, randarr::<7>())] 109 | #[case(0, 8, randarr::<9>())] 110 | #[case(7, 8, randarr::<9>())] 111 | #[should_panic = "failed to initialize"] 112 | fn panic_on_pin_init_fail( 113 | #[case] stk1_size: usize, 114 | #[case] stk2_size: usize, 115 | #[case] val: impl DebugAny, 116 | ) { 117 | let mut stk1 = newheap_fixed(stk1_size); 118 | let mut stk1 = UnsafePinnedContainer(&mut *stk1); 119 | let mut stk2 = newheap_fixed(stk2_size); 120 | let mut stk2 = UnsafePinnedContainer(&mut *stk2); 121 | 122 | let init = from_closure(|slot| slot.write(val)); 123 | if stk2_size == 0 { 124 | init.init(stk1.as_mut()); 125 | } else { 126 | init.init2(stk1.as_mut(), stk2.as_mut()); 127 | } 128 | } 129 | 130 | #[test] 131 | fn drop_boxed() { 132 | assert_eq!(DropCounter::count(), 0); 133 | 134 | let init = from_closure(|slot| slot.write(DropCounter) as &mut OpqAny); 135 | drop(init.boxed()); 136 | assert_eq!(DropCounter::count(), 1); 137 | 138 | let init = from_closure(|slot| slot.write(DropCounter) as &mut OpqAny); 139 | drop(init.pin_boxed()); 140 | assert_eq!(DropCounter::count(), 2); 141 | } 142 | 143 | #[rstest] 144 | #[case(randarr::<4>())] 145 | #[case(randarr::<8>())] 146 | #[case(randarr::<16>())] 147 | #[case(randarr::<32>())] 148 | fn fallible_constructor(#[case] val: impl DebugAny) { 149 | use crate::utils::newstk; 150 | 151 | let mut stack = newstk::<8>(); 152 | let mut heap = newheap(0); 153 | 154 | let val_size = mem::size_of_val(&val); 155 | let mut init = Some(from_closure(|slot| slot.write(val))); 156 | let init = &mut init; 157 | 158 | if val_size <= stack.len() { 159 | assert!(init.try_init(&mut stack).is_ok()); 160 | } else { 161 | assert!(init.try_init(&mut stack).is_err()); 162 | assert!(init.try_init2(&mut stack, &mut heap).is_ok()); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/container.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::Layout; 2 | use core::fmt; 3 | use core::marker::PhantomData; 4 | use core::mem::MaybeUninit; 5 | use core::ops::{Deref, DerefMut}; 6 | use core::pin::Pin; 7 | use core::ptr::NonNull; 8 | 9 | use crate::constructor::{Construct, PinConstruct, Slot}; 10 | 11 | /// A one-time container used for in-place constructions. 12 | /// 13 | /// A type that implements [`Emplace`] is called a *container*. Each container 14 | /// holds a unique memory block for object constructions. This memory block may 15 | /// have a fixed capacity or grow dynamically. A fixed-size container may reject 16 | /// construction if it lacks sufficient free space to put the target object. In 17 | /// this case, the caller is responsible for preserving the provided 18 | /// constructor, which can be done by wrapping the constructor in [`Option`]. 19 | /// 20 | /// # Safety 21 | /// 22 | /// For the implementor, 23 | /// 24 | /// - It must adhere the documented contracts of each method. 25 | /// - If [`emplace`] succeeds, the provided constructor must be consumed through 26 | /// [`construct`]. Conversely, `constructor` must remain untouched if 27 | /// [`emplace`] returns an error. Failing to follow either case results in 28 | /// *undefined behavior*. 29 | /// 30 | /// [`construct`]: PinConstruct::construct 31 | /// [`emplace`]: Self::emplace 32 | pub unsafe trait Emplace: Sized { 33 | type Ptr: core::ops::Deref; 34 | type Err; 35 | 36 | /// Consumes this container and initializes the supplied constructor in it. 37 | /// 38 | /// If `self` cannot fit the layout of the target object, it does nothing 39 | /// and returns an error. Otherwise, it consumes `constructor` and returns the 40 | /// pointer to the constructed object. 41 | fn emplace(self, constructor: C) -> Result 42 | where 43 | C: Construct; 44 | } 45 | 46 | /// A variant of [`Emplace`] used for pinned constructions. 47 | /// 48 | /// A *pinned container* holds a memory block with a stable address, which 49 | /// enables it to store objects that cannot be moved once constructed. 50 | /// 51 | /// # Safety 52 | /// 53 | /// See the safety notes of [`Emplace`]. Additionally, the implementor must 54 | /// uphold the pinning requirements for the constructed objects. 55 | pub unsafe trait PinEmplace: Emplace { 56 | /// Initializes the supplied constructor in a pinned memory block. 57 | /// 58 | /// It returns a pinned pointer to the constructed object if successful. For 59 | /// more information, see [`emplace`]. 60 | /// 61 | /// [`emplace`]: Emplace::emplace 62 | fn pin_emplace(self, constructor: C) -> Result, Self::Err> 63 | where 64 | C: PinConstruct, 65 | { 66 | struct UncheckedPinConstructor(C); 67 | unsafe impl PinConstruct for UncheckedPinConstructor { 68 | type Object = C::Object; 69 | fn layout(&self) -> Layout { 70 | self.0.layout() 71 | } 72 | unsafe fn construct(self, slot: Slot) -> NonNull { 73 | self.0.construct(slot) 74 | } 75 | } 76 | unsafe impl Construct for UncheckedPinConstructor {} 77 | self.emplace(UncheckedPinConstructor(constructor)) 78 | .map(|p| unsafe { Pin::new_unchecked(p) }) 79 | } 80 | } 81 | 82 | /// A pointer to objects stored in buffers. 83 | /// 84 | /// Containers such as `&mut [u8]` or `&mut Vec` yield this pointer type. 85 | /// Note that, unlike most pointer types, it implements `Unpin` only if `T` is 86 | /// `Unpin`. While this may seem counterintuitive, it simplifies obtaining a 87 | /// pinned reference to `T` in safe Rust, as illustrated below: 88 | /// 89 | /// ```rust 90 | /// # use dynify::{from_fn, Buffered, Dynify, Fn}; 91 | /// # use std::future::Future; 92 | /// # use std::mem::MaybeUninit; 93 | /// # use std::pin::Pin; 94 | /// # pollster::block_on(async { 95 | /// fn async_hello() -> Fn!(=> dyn Future) { 96 | /// from_fn!(|| async { String::from("Hello!") }) 97 | /// } 98 | /// 99 | /// let mut stack = MaybeUninit::<[u8; 16]>::uninit(); 100 | /// let fut: Buffered> = async_hello().init(&mut stack); 101 | /// // Pin it on the stack just as it has the inner type `T = dyn Future`. 102 | /// let fut: Pin<&mut Buffered<_>> = std::pin::pin!(fut); 103 | /// // Then project it to obtain a pinned reference to `T`. 104 | /// let fut: Pin<&mut dyn Future> = fut.project(); 105 | /// assert_eq!(fut.await, "Hello!"); 106 | /// # }); 107 | /// ``` 108 | /// 109 | /// **Tips**: `Buffered` implements `Future`, so you can simply write 110 | /// `async_hello().init(&mut stack).await` in practice. 111 | pub struct Buffered<'a, T: ?Sized>(NonNull, PhantomData<&'a mut [u8]>); 112 | impl<'a, T: ?Sized> Buffered<'a, T> { 113 | /// Constructs a new instance with the provided pointer. 114 | /// 115 | /// # Safety 116 | /// 117 | /// `ptr` must be a valid pointer to `T` and exclusive for the returned 118 | /// instance. 119 | pub unsafe fn from_raw(ptr: NonNull) -> Self { 120 | Self(ptr, PhantomData) 121 | } 122 | 123 | /// Consumes this instance, returning a raw pointer. 124 | pub fn into_raw(self) -> NonNull { 125 | let ptr = self.0; 126 | core::mem::forget(self); 127 | ptr 128 | } 129 | 130 | /// Returns a pinned mutable reference to the inner value. 131 | pub fn project(self: Pin<&mut Self>) -> Pin<&mut T> { 132 | unsafe { 133 | let this = Pin::into_inner_unchecked(self); 134 | Pin::new_unchecked(this) 135 | } 136 | } 137 | 138 | /// Returns a pinned immutable reference to the inner value. 139 | pub fn project_ref(self: Pin<&Self>) -> Pin<&T> { 140 | unsafe { 141 | let this = Pin::into_inner_unchecked(self); 142 | Pin::new_unchecked(this) 143 | } 144 | } 145 | } 146 | 147 | // Pretend `Buffered` owns the value of `T` rather than just a pointer to it. 148 | // This, along with the `Buffered::project*` APIs, makes it easy to obtain a 149 | // pinned reference to `T` in safe Rust. But the downside is that this prevents 150 | // `Buffered` from being `Unpin` if `T` is not `Unpin`, which is unexpected for 151 | // a pointer type. Nevertheless, in most cases, pinning a pointer is not 152 | // particularly useful. 153 | // 154 | // Besides, we cannot provide `Buffered::into_pin` even if the container has a 155 | // `'static` lifetime. This is because containers do not guarantee that the 156 | // memory region allocated to us will not be overwritten if `Buffered` is leaked 157 | // through `std::mem::forget`. This violation undermines the drop guarantee 158 | // required by `Pin`. For more information, see . 159 | impl Unpin for Buffered<'_, T> {} 160 | impl Drop for Buffered<'_, T> { 161 | fn drop(&mut self) { 162 | if core::mem::needs_drop::() { 163 | unsafe { self.0.drop_in_place() } 164 | } 165 | } 166 | } 167 | 168 | impl Deref for Buffered<'_, T> { 169 | type Target = T; 170 | fn deref(&self) -> &Self::Target { 171 | unsafe { self.0.as_ref() } 172 | } 173 | } 174 | impl DerefMut for Buffered<'_, T> { 175 | fn deref_mut(&mut self) -> &mut Self::Target { 176 | unsafe { self.0.as_mut() } 177 | } 178 | } 179 | 180 | impl fmt::Debug for Buffered<'_, T> { 181 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 182 | T::fmt(self, f) 183 | } 184 | } 185 | 186 | impl core::future::Future for Buffered<'_, T> 187 | where 188 | T: ?Sized + core::future::Future, 189 | { 190 | type Output = T::Output; 191 | fn poll( 192 | self: Pin<&mut Self>, 193 | cx: &mut core::task::Context<'_>, 194 | ) -> core::task::Poll { 195 | self.project().poll(cx) 196 | } 197 | } 198 | 199 | /// An error thrown by buffers with fixed capacity. 200 | #[derive(Debug)] 201 | pub struct OutOfCapacity; 202 | impl fmt::Display for OutOfCapacity { 203 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 204 | f.write_str("out of capacity") 205 | } 206 | } 207 | 208 | unsafe impl<'a, T: ?Sized, const N: usize> Emplace for &'a mut MaybeUninit<[u8; N]> { 209 | type Ptr = Buffered<'a, T>; 210 | type Err = OutOfCapacity; 211 | 212 | fn emplace(self, constructor: C) -> Result 213 | where 214 | C: Construct, 215 | { 216 | let uninit_slice: &mut [MaybeUninit; N] = unsafe { core::mem::transmute(self) }; 217 | uninit_slice.emplace(constructor) 218 | } 219 | } 220 | unsafe impl<'a, T: ?Sized, const N: usize> Emplace for &'a mut [MaybeUninit; N] { 221 | type Ptr = Buffered<'a, T>; 222 | type Err = OutOfCapacity; 223 | 224 | fn emplace(self, constructor: C) -> Result 225 | where 226 | C: Construct, 227 | { 228 | self.as_mut_slice().emplace(constructor) 229 | } 230 | } 231 | unsafe impl<'a, T: ?Sized> Emplace for &'a mut [MaybeUninit] { 232 | type Ptr = Buffered<'a, T>; 233 | type Err = OutOfCapacity; 234 | 235 | fn emplace(self, constructor: C) -> Result 236 | where 237 | C: Construct, 238 | { 239 | unsafe { 240 | let layout = constructor.layout(); 241 | let slot = buf_emplace(self, layout)?; 242 | let ptr = slot.as_ptr(); 243 | 244 | let init = constructor.construct(slot); 245 | validate_slot(ptr, layout, init); 246 | Ok(Buffered::from_raw(init)) 247 | } 248 | } 249 | } 250 | unsafe fn buf_emplace( 251 | buf: &mut [MaybeUninit], 252 | layout: Layout, 253 | ) -> Result, OutOfCapacity> { 254 | if layout.size() == 0 { 255 | return Ok(dangling_slot(layout)); 256 | } 257 | 258 | let start = buf.as_mut_ptr(); 259 | let align_offset = start.align_offset(layout.align()); 260 | let total_bytes = align_offset + layout.size(); 261 | 262 | if total_bytes > buf.len() { 263 | return Err(OutOfCapacity); 264 | } 265 | let ptr = start.add(align_offset).cast::(); 266 | Ok(Slot::new_unchecked(NonNull::new_unchecked(ptr))) 267 | } 268 | 269 | #[cfg(feature = "alloc")] 270 | mod __alloc { 271 | use alloc::boxed::Box; 272 | use alloc::vec::Vec; 273 | use core::convert::Infallible; 274 | 275 | use super::*; 276 | 277 | /// A unit type to perform constructions in [`Box`]. 278 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 279 | #[derive(Debug)] 280 | pub struct Boxed; 281 | 282 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 283 | unsafe impl Emplace for Boxed { 284 | type Ptr = Box; 285 | type Err = Infallible; 286 | 287 | fn emplace(self, constructor: C) -> Result 288 | where 289 | C: Construct, 290 | { 291 | unsafe { 292 | let layout = constructor.layout(); 293 | let slot = box_emlace(layout); 294 | let ptr = slot.as_ptr(); 295 | 296 | // Recycle the allocated memory to prevent memory leaks if 297 | // `construct()` panics. 298 | let clean_on_panic = crate::utils::defer(|| { 299 | if layout.size() != 0 { 300 | alloc::alloc::dealloc(ptr.as_ptr(), layout) 301 | } 302 | }); 303 | let init = constructor.construct(slot); 304 | validate_slot(ptr, layout, init); 305 | 306 | core::mem::forget(clean_on_panic); 307 | Ok(Box::from_raw(init.as_ptr())) 308 | } 309 | } 310 | } 311 | // Pinned box 312 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 313 | unsafe impl PinEmplace for Boxed {} 314 | unsafe fn box_emlace(layout: Layout) -> Slot<'static> { 315 | if layout.size() == 0 { 316 | return dangling_slot(layout); 317 | } 318 | // SAFETY: `layout` is non-zero in size, 319 | let ptr = NonNull::new(alloc::alloc::alloc(layout)) 320 | .unwrap_or_else(|| alloc::alloc::handle_alloc_error(layout)); 321 | Slot::new_unchecked(ptr) 322 | } 323 | 324 | // TODO: pinned vector? 325 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 326 | unsafe impl<'a, T: ?Sized> Emplace for &'a mut Vec> { 327 | type Ptr = Buffered<'a, T>; 328 | type Err = Infallible; 329 | 330 | fn emplace(self, constructor: C) -> Result 331 | where 332 | C: Construct, 333 | { 334 | unsafe { 335 | let layout = constructor.layout(); 336 | let slot = vec_emplace(self, layout); 337 | let ptr = slot.as_ptr(); 338 | 339 | let init = constructor.construct(slot); 340 | validate_slot(ptr, layout, init); 341 | Ok(Buffered::from_raw(init)) 342 | } 343 | } 344 | } 345 | unsafe fn vec_emplace(vec: &mut Vec>, layout: Layout) -> Slot<'_> { 346 | if layout.size() == 0 { 347 | return dangling_slot(layout); 348 | } 349 | 350 | let mut buf = vec.as_mut_ptr(); 351 | let mut align_offset = buf.align_offset(layout.align()); 352 | let total_bytes = align_offset + layout.size(); 353 | 354 | if total_bytes > vec.capacity() { 355 | vec.reserve(layout.size() + layout.align() - vec.len()); 356 | buf = vec.as_mut_ptr(); 357 | align_offset = buf.align_offset(layout.align()); 358 | } 359 | let slot = buf.add(align_offset).cast::(); 360 | Slot::new_unchecked(NonNull::new_unchecked(slot)) 361 | } 362 | } 363 | #[cfg(feature = "alloc")] 364 | pub use __alloc::*; 365 | 366 | #[cfg(feature = "smallvec")] 367 | mod __smallvec { 368 | use core::convert::Infallible; 369 | use core::mem::MaybeUninit; 370 | 371 | use smallvec::{Array, SmallVec}; 372 | 373 | use super::*; 374 | 375 | #[cfg_attr(docsrs, doc(cfg(feature = "smallvec")))] 376 | unsafe impl<'a, A, T> Emplace for &'a mut SmallVec 377 | where 378 | A: Array>, 379 | T: ?Sized, 380 | { 381 | type Ptr = Buffered<'a, T>; 382 | type Err = Infallible; 383 | 384 | fn emplace(self, constructor: C) -> Result 385 | where 386 | C: Construct, 387 | { 388 | unsafe { 389 | let layout = constructor.layout(); 390 | let slot = small_vec_emplace(self, layout); 391 | let ptr = slot.as_ptr(); 392 | 393 | let init = constructor.construct(slot); 394 | validate_slot(ptr, layout, init); 395 | Ok(Buffered::from_raw(init)) 396 | } 397 | } 398 | } 399 | unsafe fn small_vec_emplace(vec: &mut SmallVec, layout: Layout) -> Slot<'_> 400 | where 401 | A: Array>, 402 | { 403 | if layout.size() == 0 { 404 | return dangling_slot(layout); 405 | } 406 | 407 | let mut buf = vec.as_mut_ptr(); 408 | let mut align_offset = buf.align_offset(layout.align()); 409 | let total_bytes = align_offset + layout.size(); 410 | 411 | if total_bytes > vec.capacity() { 412 | vec.reserve(layout.size() + layout.align() - vec.len()); 413 | buf = vec.as_mut_ptr(); 414 | align_offset = buf.align_offset(layout.align()); 415 | } 416 | let slot = buf.add(align_offset).cast::(); 417 | Slot::new_unchecked(NonNull::new_unchecked(slot)) 418 | } 419 | } 420 | 421 | // TODO: is it possible to use strict provenance APIs? 422 | unsafe fn dangling_slot<'a>(layout: Layout) -> Slot<'a> { 423 | Slot::new_unchecked(NonNull::new_unchecked(layout.align() as *mut u8)) 424 | } 425 | 426 | fn validate_slot(ptr: NonNull, layout: Layout, init: NonNull) { 427 | if cfg!(debug_assertions) { 428 | let init_ptr = init.cast::(); 429 | assert_eq!(init_ptr, ptr, "initialized address mismatches"); 430 | let init_layout = unsafe { Layout::for_value(init.as_ref()) }; 431 | assert_eq!(init_layout, layout, "initialized layout mismatches"); 432 | } 433 | } 434 | 435 | #[cfg(test)] 436 | #[cfg_attr(coverage_nightly, coverage(off))] 437 | #[path = "container_tests.rs"] 438 | mod tests; 439 | -------------------------------------------------------------------------------- /src/container_tests.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::marker::PhantomPinned; 3 | use std::pin::pin; 4 | 5 | use rstest::rstest; 6 | #[cfg(feature = "smallvec")] 7 | use smallvec::SmallVec; 8 | 9 | use super::*; 10 | use crate::utils::*; 11 | use crate::{from_closure, Dynify}; 12 | 13 | trait DebugEmplace: Emplace { 14 | type __Err: std::fmt::Debug; 15 | } 16 | impl DebugEmplace for C 17 | where 18 | C: Emplace, 19 | C::Err: std::fmt::Debug, 20 | { 21 | type __Err = C::Err; 22 | } 23 | 24 | #[rstest] 25 | #[case(&mut MaybeUninit::<[u8; 12]>::uninit())] 26 | #[case(&mut [MaybeUninit::new(0u8); 12])] 27 | #[case(&mut [MaybeUninit::new(0u8); 12] as &mut [MaybeUninit])] 28 | fn fix_sized_containers(#[case] c: &mut C) 29 | where 30 | C: ?Sized, 31 | for<'a> &'a mut C: DebugEmplace, 32 | { 33 | let inp = randarr::<8>(); 34 | let init = from_closure(|slot| slot.write(inp) as &mut OpqAny); 35 | let out = c.emplace(init).unwrap(); 36 | assert_eq!(out.downcast_ref::<[u8; 8]>(), Some(&inp), "init ok"); 37 | drop(out); 38 | 39 | let inp = randarr::<14>(); 40 | let init = from_closure(|slot| slot.write(inp) as &mut OpqAny); 41 | assert!(c.emplace(init).is_err(), "init err"); 42 | } 43 | 44 | #[rstest] 45 | #[case(Boxed)] 46 | #[case(&mut Vec::>::new())] 47 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 0]>::new()) )] 48 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 12]>::new()) )] 49 | fn allocated_containers(#[case] c: impl DebugEmplace) { 50 | let inp = randarr::<16>(); 51 | let init = from_closure(|slot| slot.write(inp) as &mut OpqAny); 52 | let out = c.emplace(init).unwrap(); 53 | assert_eq!(out.downcast_ref::<[u8; 16]>(), Some(&inp)); 54 | } 55 | 56 | #[rstest] 57 | #[case(Boxed)] 58 | #[case(&mut [MaybeUninit::new(0u8); 64])] 59 | #[case(&mut [MaybeUninit::uninit(); 64] as &mut [MaybeUninit])] 60 | #[case(&mut Vec::>::new())] 61 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 0]>::new()) )] 62 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 12]>::new()) )] 63 | fn init_object_of_random_layout(#[case] c: impl DebugEmplace) { 64 | macro_rules! select_layout { 65 | ($rand:ident, $($align:literal),+) => {$( 66 | if $rand == $align { 67 | #[repr(align($align))] 68 | struct Test(T); 69 | let inp = randarr::<16>(); 70 | let init = from_closure(|slot| slot.write(Test(inp)) as &mut OpqAny); 71 | let out = c.emplace(init).unwrap(); 72 | let out = out.downcast_ref::>().unwrap(); 73 | assert_eq!(&out.0, &inp); 74 | return; 75 | } 76 | )*}; 77 | } 78 | 79 | let rand = 1 << fastrand::usize(..6); 80 | select_layout!(rand, 1, 2, 4, 8, 16, 32); 81 | unreachable!(); 82 | } 83 | 84 | #[rstest] 85 | #[case(Boxed)] 86 | #[case(&mut Vec::>::new())] 87 | #[case(&mut [] as &mut [MaybeUninit])] 88 | #[case(&mut [] as &mut [MaybeUninit; 0])] 89 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 0]>::new()) )] 90 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 12]>::new()) )] 91 | fn never_fail_on_zst(#[case] c: impl DebugEmplace) { 92 | #[repr(align(4096))] 93 | struct Zst; 94 | 95 | let init = from_closure(|slot| slot.write(Zst) as &mut OpqAny); 96 | let out = c.emplace(init).unwrap(); 97 | let out = out.downcast_ref::().unwrap(); 98 | assert!(std::ptr::from_ref(out).is_aligned()); 99 | } 100 | 101 | #[rstest] 102 | #[case(&mut newstk::<24>())] 103 | #[case(&mut newstk::<24>() as &mut [MaybeUninit])] 104 | #[case(&mut Vec::>::new())] 105 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 0]>::new()) )] 106 | #[cfg_attr(feature = "smallvec", case(&mut SmallVec::<[MaybeUninit; 12]>::new()) )] 107 | fn drop_buffered<'a>(#[case] c: impl 'a + DebugEmplace>) { 108 | let init = from_closure(|slot| slot.write(DropCounter) as &mut OpqAny); 109 | let out = c.emplace(init).unwrap(); 110 | assert_eq!(DropCounter::count(), 0); 111 | drop(out); 112 | assert_eq!(DropCounter::count(), 1); 113 | } 114 | 115 | #[test] 116 | fn unpin_buffered() { 117 | let mut stack = newstk::<16>(); 118 | let init = from_closure(|slot| slot.write(123)); 119 | let val: Pin<&mut Buffered> = pin!(init.init(&mut stack)); 120 | let _: &mut Buffered = Pin::into_inner(val); 121 | } 122 | 123 | #[test] 124 | fn project_buffered() { 125 | let mut stack = newstk::<16>(); 126 | let init = from_closure(|slot| slot.write(PhantomPinned)); 127 | let mut buf: Pin<&mut Buffered> = pin!(init.init(&mut stack)); 128 | let _: Pin<&mut PhantomPinned> = buf.as_mut().project(); 129 | let _: Pin<&PhantomPinned> = buf.as_ref().project_ref(); 130 | } 131 | 132 | #[test] 133 | fn project_pinned_buffered() { 134 | let mut stack = newstk::<16>(); 135 | let init = from_closure(|slot| slot.write(123)); 136 | let mut val: Pin<&mut Buffered> = pin!(init.init(&mut stack)); 137 | let _: Pin<&mut usize> = val.as_mut().project(); 138 | let _: Pin<&usize> = val.as_ref().project_ref(); 139 | } 140 | 141 | #[pollster::test] 142 | async fn buffered_future() { 143 | let mut stack = newstk::<16>(); 144 | let inp = randstr(8..64); 145 | let init = from_closure(|slot| slot.write(async { inp.clone() }) as &mut OpqStrFut); 146 | let fut: Buffered = stack.emplace(init).unwrap(); 147 | let out = fut.await; 148 | assert_eq!(out, inp); 149 | } 150 | 151 | #[test] 152 | fn buffered_raw_ptr() { 153 | let mut stack = newstk::<16>(); 154 | let stack_ptr = std::ptr::from_ref(&stack); 155 | let init = from_closure(|slot| slot.write([0u8; 4])); 156 | let val: Buffered<[u8; 4]> = stack.emplace(init).unwrap(); 157 | let val_ptr = val.into_raw().as_ptr(); 158 | assert_eq!(val_ptr as *const (), stack_ptr as *const ()); 159 | } 160 | 161 | #[test] 162 | fn default_pin_emplace() { 163 | let inp = randarr::<16>(); 164 | let init = from_closure(|slot| slot.write(inp) as &mut OpqAny); 165 | let out = Boxed.pin_emplace(init).unwrap(); 166 | assert_eq!(out.downcast_ref::<[u8; 16]>(), Some(&inp)); 167 | } 168 | 169 | #[test] 170 | #[should_panic = "just panic"] 171 | fn clean_up_boxed_zst_on_panic() { 172 | let _ = from_closure::<(), (), _>(|_| panic!("just panic")).boxed(); 173 | } 174 | 175 | #[test] 176 | #[should_panic = "just panic"] 177 | fn clean_up_boxed_on_panic() { 178 | let _ = from_closure::(|_| panic!("just panic")).boxed(); 179 | } 180 | -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::Layout; 2 | use core::marker::PhantomData; 3 | use core::ptr::NonNull; 4 | 5 | use crate::constructor::{Construct, Opaque, PinConstruct, Slot}; 6 | use crate::receiver::Receiver; 7 | 8 | /// A constructor for the return type of functions. 9 | #[must_use = "constructor must be initialized"] 10 | pub struct Fn { 11 | layout: Layout, 12 | init: unsafe fn(Slot, Args) -> &mut Opaque, 13 | args: Args, 14 | } 15 | 16 | unsafe impl PinConstruct for Fn { 17 | type Object = Ret; 18 | unsafe fn construct(self, slot: Slot) -> NonNull { 19 | let ptr = (self.init)(slot, self.args); 20 | NonNull::from(ptr.as_mut()) 21 | } 22 | fn layout(&self) -> Layout { 23 | self.layout 24 | } 25 | } 26 | unsafe impl Construct for Fn {} 27 | 28 | /// A helper struct to display friendly errors. 29 | /// 30 | /// For the emitted errors, see `tests/ui/from_fn_with_closure_fail.stderr`. 31 | pub struct MustNotBeClosure; 32 | 33 | /// Creates a constructor for the return type of the specified function. 34 | /// 35 | /// All arguments required for `F` should be packed into `args` as a tuple. 36 | /// `args` is passed to `init` along with a slot to store the returned value 37 | /// when the returned instance is ready to be constructed. 38 | /// 39 | /// # Safety 40 | /// 41 | /// `init` may not write data to the supplied slot of different layouts than the 42 | /// return type of `F`. 43 | #[inline(always)] 44 | pub unsafe fn from_bare_fn( 45 | _: fn(MustNotBeClosure) -> F, 46 | args: Args, 47 | init: unsafe fn(Slot, Args) -> &mut Opaque, 48 | ) -> Fn 49 | where 50 | F: Function, 51 | Ret: ?Sized, 52 | { 53 | Fn { 54 | layout: Layout::new::(), 55 | init, 56 | args, 57 | } 58 | } 59 | 60 | /// Creates a constructor for the return type of the specified method. 61 | /// 62 | /// A method is a function of which receiver is sealed with [`Receiver::seal`]. 63 | /// 64 | /// # Safety 65 | /// 66 | /// See [`from_bare_fn`]. 67 | #[inline(always)] 68 | pub unsafe fn from_method( 69 | _: fn(MustNotBeClosure) -> F, 70 | args: Args, 71 | init: unsafe fn(Slot, F::SealedArgs) -> &mut Opaque, 72 | ) -> Fn 73 | where 74 | F: Method, 75 | Ret: ?Sized, 76 | { 77 | pub struct MethodAsBareFn(PhantomData<(fn(Args), F)>); 78 | impl Function for MethodAsBareFn 79 | where 80 | F: Method, 81 | { 82 | type Ret = F::Ret; 83 | } 84 | let args = F::seal_args(args); 85 | from_bare_fn(|_| MethodAsBareFn::(PhantomData), args, init) 86 | } 87 | 88 | /// A blanked trait implemented for arbitrary functions. 89 | pub trait Function { 90 | type Ret; 91 | } 92 | /// Wraps a function with its receiver type sealed. 93 | pub trait Method: Function { 94 | type SealedArgs; 95 | fn seal_args(args: Args) -> Self::SealedArgs; 96 | } 97 | macro_rules! impl_function { 98 | (-> $R:ident) => { 99 | impl $R, $R> Function<()> for Fn { 100 | type Ret = $R; 101 | } 102 | }; 103 | ($A:ident $(,$Args:ident)* -> $R:ident) => { 104 | impl Function<($A, $($Args,)*)> for Fn 105 | where 106 | Fn: FnOnce($A, $($Args,)*) -> $R, 107 | { 108 | type Ret = $R; 109 | } 110 | impl Method<($A, $($Args,)*)> for Fn 111 | where 112 | $A: Receiver, 113 | Fn: FnOnce($A, $($Args,)*) -> $R, 114 | { 115 | type SealedArgs = (<$A as Receiver>::Sealed, $($Args,)*); 116 | #[allow(non_snake_case)] 117 | #[inline(always)] 118 | fn seal_args(($A, $($Args,)*): ($A, $($Args,)*)) -> Self::SealedArgs { 119 | (Receiver::seal($A), $($Args,)*) 120 | } 121 | } 122 | impl_function!($($Args),* -> $R); 123 | }; 124 | } 125 | impl_function!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P -> R); // 16 arguments 126 | 127 | doc_macro! { 128 | /// Creates a constructor for static functions. 129 | /// 130 | /// It accepts as its parameters the target function followed by all the 131 | /// arguments required to invoke that function, returning a constructor for 132 | /// the return type of the function. The type of returned constructors can 133 | /// be obtained with [`Fn`]. 134 | /// 135 | /// The provided function must be a static item which can be resolved at 136 | /// compile-time; therefore, closures are not supported. For methods, the 137 | /// second parameter must be `self`; otherwise, the returned constructor 138 | /// falls back to a bare function constructor. 139 | /// 140 | /// # Example 141 | /// 142 | /// ```rust 143 | /// # use dynify::{from_fn, Fn}; 144 | /// # use std::future::Future; 145 | /// async fn read_string(path: &str) -> String { String::new() } 146 | /// let path = "/tmp/file"; 147 | /// let _: Fn!(_ => dyn Future) = from_fn!(read_string, path); 148 | /// ``` 149 | /// 150 | /// [`Fn`]: crate::Fn 151 | #[macro_export] 152 | macro from_fn { 153 | ($f:expr, $self:ident $(,$args:ident)*) => {}; 154 | ($f:expr $(,$args:ident)*) => {}; 155 | } { 156 | ($f:expr, $self:ident $(,$args:ident)* $(,)?) => { $crate::__from_fn!([$self] $f, $self, $($args,)*) }; 157 | ($f:expr $(,$args:ident)* $(,)?) => { $crate::__from_fn!([] $f, $($args,)*) }; 158 | } 159 | } 160 | #[doc(hidden)] 161 | #[macro_export] 162 | macro_rules! __from_fn { 163 | ([self] $f:expr, $self:ident, $($args:ident,)*) => { 164 | // SAFETY: 165 | // - The `Function` trait ensures the layout of the object written to 166 | // `slot` matches the return type of the specified function. 167 | // - `Opaque` prevents misuse of the initialized pointers. 168 | // - Moving the return value into `slot` does not require pinned memory, 169 | // therefore it doesn't matter where the object is stored. 170 | unsafe { 171 | $crate::r#priv::from_method( 172 | |_| $f, 173 | ($self, $($args,)*), 174 | |slot, (this, $($args,)*)| { 175 | let this = $crate::r#priv::Receiver::unseal(this); 176 | let return_value = ($f)(this, $($args,)*); 177 | let object = slot.cast().write(return_value); 178 | object as &mut $crate::Opaque::<_> 179 | }, 180 | ) 181 | } 182 | }; 183 | ([$($_:ident)?] $f:expr, $($args:ident,)*) => { 184 | // SAFETY: See the comment above. 185 | unsafe { 186 | $crate::r#priv::from_bare_fn( 187 | |_| $f, 188 | ($($args,)*), 189 | |slot, ($($args,)*)| { 190 | let return_value = ($f)($($args,)*); 191 | let object = slot.cast().write(return_value); 192 | object as &mut $crate::Opaque::<_> 193 | }, 194 | ) 195 | } 196 | }; 197 | } 198 | 199 | doc_macro! { 200 | /// Determines the constructor type of a function. 201 | /// 202 | /// It accepts as its parameters a list of argument types of the target 203 | /// function followed by a fat-arrow (`=>`) and the return type of that 204 | /// function, returning the type of constructors created by [`from_fn`]. 205 | /// 206 | /// For method types, which are functions with a receiver type such as 207 | /// `&Self`, `&mut Self` or `Box` as the first parameter, this macro 208 | /// automatically selects an appropriate sealed type to make the constructor 209 | /// *dyn compatible*. 210 | /// 211 | /// Note that the receiver type should not include the full path. Using 212 | /// types like `std::boxed::Box` will lead to an incorrect matching 213 | /// and cause the constructor type to be *dyn incompatible*. Nevertheless, 214 | /// it's not necessary to import these types beforehand. 215 | /// 216 | /// If none of the supported receiver types matches, it falls back to a bare 217 | /// function type. 218 | /// 219 | /// # Example 220 | /// 221 | /// ```rust 222 | /// # use dynify::{from_fn, Fn}; 223 | /// # use std::future::Future; 224 | /// async fn fetch_something(uri: &str) -> String { 225 | /// String::from("** mysterious text **") 226 | /// } 227 | /// fn dyn_fetch_something(uri: &str) -> Fn!(&str => dyn '_ + Future) { 228 | /// from_fn!(fetch_something, uri) 229 | /// } 230 | /// ``` 231 | /// 232 | /// [`from_fn`]: crate::from_fn 233 | #[macro_export] 234 | macro Fn { 235 | (_ => $ret:ty) => {}; 236 | ($Self:ty $(,$args:ty)* => $ret:ty) => {}; 237 | ($($args:ty),* => $ret:ty) => {}; 238 | } { 239 | (_ => $ret:ty) => { $crate::r#priv::Fn<_, $ret> }; 240 | ($($args:tt)*) => { $crate::__Fn!($($args)*) }; 241 | } 242 | } 243 | #[doc(hidden)] 244 | #[macro_export] 245 | macro_rules! __Fn { 246 | (&$($lt:lifetime)? Self $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::RefSelf$(<$lt>)*, $($args,)*), $ret> }; 247 | (&$($lt:lifetime)? mut Self $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::RefMutSelf$(<$lt>)*, $($args,)*), $ret> }; 248 | (Box $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::BoxSelf, $($args,)*), $ret> }; 249 | (Rc $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::RcSelf, $($args,)*), $ret> }; 250 | (Arc $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::ArcSelf, $($args,)*), $ret> }; 251 | 252 | (Pin<&$($lt:lifetime)? Self> $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::PinRefSelf$(<$lt>)*, $($args,)*), $ret> }; 253 | (Pin<&$($lt:lifetime)? mut Self> $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::PinRefMutSelf$(<$lt>)*, $($args,)*), $ret> }; 254 | (Pin> $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::PinBoxSelf, $($args,)*), $ret> }; 255 | (Pin> $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::PinRcSelf, $($args,)*), $ret> }; 256 | (Pin> $(,$args:ty)* => $ret:ty) => { $crate::r#priv::Fn<($crate::r#priv::PinArcSelf, $($args,)*), $ret> }; 257 | 258 | ($($args:ty),* => $ret:ty) => { $crate::r#priv::Fn<($($args,)*), $ret> }; 259 | } 260 | 261 | #[cfg(test)] 262 | #[cfg_attr(coverage_nightly, coverage(off))] 263 | #[path = "function_tests.rs"] 264 | mod tests; 265 | -------------------------------------------------------------------------------- /src/function_tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(mismatched_lifetime_syntaxes)] 2 | 3 | use std::any::Any; 4 | use std::pin::{pin, Pin}; 5 | 6 | use super::*; 7 | use crate::receiver::*; 8 | use crate::utils::{randstr, DropCounter, StrFut}; 9 | use crate::{Dynify, PinDynify}; 10 | 11 | #[test] 12 | fn return_type_layout_ok() { 13 | use std::any::Any; 14 | use std::convert::Infallible; 15 | 16 | pub const fn layout>(_: &F) -> Layout { 17 | Layout::new::() 18 | } 19 | 20 | fn f1(_: usize, _: usize) -> usize { 21 | todo!() 22 | } 23 | fn f2(_: &str) -> &str { 24 | todo!() 25 | } 26 | fn f3(_: String, _: Vec, _: &dyn Any) -> Box { 27 | todo!() 28 | } 29 | fn f4(_: usize, _: usize) -> Infallible { 30 | todo!() 31 | } 32 | 33 | assert_eq!(layout(&f1), Layout::new::()); 34 | assert_eq!(layout(&f2), Layout::new::<&str>()); 35 | assert_eq!(layout(&f3), Layout::new::>()); 36 | assert_eq!(layout(&f4), Layout::new::()); 37 | } 38 | 39 | #[pollster::test] 40 | async fn from_bare_fn_ok() { 41 | thread_local! { 42 | static DATA: String = randstr(8..64); 43 | } 44 | let init: Fn!(=> StrFut) = from_fn!(|| async { DATA.with(<_>::clone) }); 45 | assert_eq!(init.pin_boxed().await, DATA.with(<_>::clone)); 46 | } 47 | 48 | #[test] 49 | fn from_method_ok() { 50 | struct Test<'a>(&'a str); 51 | impl Test<'_> { 52 | fn test(&self) -> Fn!(&Self => dyn Any) { 53 | from_fn!(|this: &Self| this.0.to_owned(), self) 54 | } 55 | } 56 | let data = randstr(8..64); 57 | let test = Test(&data); 58 | let init = test.test(); 59 | assert_eq!(init.boxed().downcast_ref::(), Some(&data)); 60 | } 61 | 62 | #[test] 63 | #[allow(clippy::drop_non_drop)] 64 | fn from_fn_drop_ok() { 65 | let init: Fn!(=> dyn Any) = from_fn!(|| DropCounter); 66 | // Nothing happens if a function pointer gets dropped. 67 | drop(init); 68 | assert_eq!(DropCounter::count(), 0); 69 | 70 | let arg = DropCounter; 71 | let init: Fn!(DropCounter => dyn Any) = from_fn!(|arg| { std::mem::forget(arg) }, arg); 72 | // Nothing happens if `DropCounter` gets forgotten. 73 | let _ = init.boxed(); 74 | assert_eq!(DropCounter::count(), 0); 75 | 76 | let arg = DropCounter; 77 | let init: Fn!(DropCounter => dyn Any) = from_fn!(|_| DropCounter, arg); 78 | // Count increments if `DropCounter` gets dropped. 79 | drop(init); 80 | assert_eq!(DropCounter::count(), 1); 81 | } 82 | 83 | struct Test; 84 | #[allow(clippy::boxed_local)] 85 | impl Test { 86 | fn ref_fn(&self) {} 87 | fn ref_mut_fn(&mut self) {} 88 | fn box_fn(self: Box) {} 89 | fn rc_fn(self: std::rc::Rc) {} 90 | fn arc_fn(self: std::sync::Arc) {} 91 | fn pin_fn(self: Pin<&mut Self>) {} 92 | fn pin_box_fn(self: Pin>) {} 93 | } 94 | 95 | #[rustfmt::skip] 96 | impl Test { 97 | fn as_ref(&self) -> Fn!(&Self => ()) { from_fn!(Self::ref_fn, self) } 98 | fn as_ref_mut(&mut self) -> Fn!(&mut Self => ()) { from_fn!(Self::ref_mut_fn, self) } 99 | fn as_box(self: Box) -> Fn!(Box => ()) { from_fn!(Self::box_fn, self) } 100 | fn as_rc(self: std::rc::Rc) -> Fn!(Rc => ()) { from_fn!(Self::rc_fn, self) } 101 | fn as_arc(self: std::sync::Arc) -> Fn!(Arc => ()) { from_fn!(Self::arc_fn, self) } 102 | fn as_pin(self: Pin<&mut Self>) -> Fn!(Pin<&mut Self> => ()) { from_fn!(Self::pin_fn, self) } 103 | fn as_pin_box(self: Pin>) -> Fn!(Pin> => ()) { from_fn!(Self::pin_box_fn, self) } 104 | } 105 | #[test] 106 | fn receiver_matching() { 107 | let _: Fn<(RefSelf,), ()> = Test.as_ref(); 108 | let _: Fn<(RefMutSelf,), ()> = Test.as_ref_mut(); 109 | let _: Fn<(BoxSelf,), ()> = Box::new(Test).as_box(); 110 | let _: Fn<(RcSelf,), ()> = std::rc::Rc::new(Test).as_rc(); 111 | let _: Fn<(ArcSelf,), ()> = std::sync::Arc::new(Test).as_arc(); 112 | let _: Fn<(crate::receiver::Pin,), ()> = pin!(Test).as_pin(); 113 | let _: Fn<(crate::receiver::Pin,), ()> = Box::pin(Test).as_pin_box(); 114 | } 115 | 116 | #[rustfmt::skip] 117 | impl Test { 118 | fn as_bare_ref(this: &Self) -> Fn!(&Test => ()) { from_fn!(Self::ref_fn, this) } 119 | fn as_bare_box(this: Box) -> Fn!(std::boxed::Box => ()) { from_fn!(Self::box_fn, this) } 120 | } 121 | #[test] 122 | fn receiver_match_fallback() { 123 | let _: Fn<(&Test,), ()> = Test::as_bare_ref(&Test); 124 | let _: Fn<(Box,), ()> = Test::as_bare_box(Box::new(Test)); 125 | } 126 | -------------------------------------------------------------------------------- /src/lib.md: -------------------------------------------------------------------------------- 1 | Add dyn compatible variant to your async trait with 🦕 dynify! 2 | 3 | ## The problem 4 | 5 | Currently, dynamic dispatch on AFIT (Async Fn In Trait) is not possible in Rust. For the following 6 | code: 7 | 8 | ```rust,compile_fail 9 | trait AsyncRead { 10 | async fn read_to_string(&mut self) -> String; 11 | } 12 | 13 | async fn dynamic_dispatch(reader: &dyn AsyncRead) { 14 | // ... 15 | } 16 | ``` 17 | 18 | compiler will give you errors like this: 19 | 20 | ```text 21 | error[E0038]: the trait `AsyncRead` cannot be made into an object 22 | --> src/lib.rs:12:36 23 | | 24 | 7 | async fn dynamic_dispatch(reader: &dyn AsyncRead) { 25 | | ^^^^^^^^^^^^^ `AsyncRead` cannot be made into an object 26 | | 27 | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically 28 | ``` 29 | 30 | ## The solution 31 | 32 | dynify implements partial features of the experimental 33 | [in-place initialization proposal](https://github.com/rust-lang/lang-team/issues/336), which makes 34 | it possible to create a dyn compatible variant for `AsyncRead`: 35 | 36 | ```rust 37 | # trait AsyncRead { 38 | # async fn read_to_string(&mut self) -> String; 39 | # } 40 | use dynify::{from_fn, Dynify, Fn}; 41 | use std::future::Future; 42 | use std::mem::MaybeUninit; 43 | 44 | trait DynAsyncRead { 45 | // `Fn!()` returns a dyn compatible type for the original async function. 46 | fn read_to_string(&mut self) -> Fn!(&mut Self => dyn '_ + Future); 47 | } 48 | impl DynAsyncRead for T { 49 | fn read_to_string(&mut self) -> Fn!(&mut Self => dyn '_ + Future) { 50 | // While `from_fn!()` lets you create a constructor of such type. 51 | from_fn!(T::read_to_string, self) 52 | } 53 | } 54 | 55 | // Now we can use dynamic dispatched `AsyncRead`! 56 | async fn dynamic_dispatch(reader: &mut dyn DynAsyncRead) { 57 | // Prepare containers, we will see how they are used soon. 58 | let mut stack = [MaybeUninit::::uninit(); 16]; 59 | let mut heap = Vec::>::new(); 60 | 61 | // `read_to_string` returns a constructor, which can be considered as a 62 | // function pointer to `AsyncRead::read_to_string` along with all necessary 63 | // arguments to invoke it. 64 | let init = reader.read_to_string(); 65 | // Therefore, we need to initialize the constructor to obtain the actual 66 | // `Future` before going on. `Dynify` offers a set of convenient methods to 67 | // do this. Since the size of the `Future` object is determined at runtime, 68 | // we can't know in advance what size containers can fit it. Here we use 69 | // `init2` to select a appropriate container for it. It accepts two 70 | // containers: 71 | let fut = init.init2( 72 | // the first one is allocated on the stack, allowing us to put the 73 | // object there to avoid relatively costly heap allocations. 74 | &mut stack, 75 | // the second one is allocated on the heap, serving as a fallback if the 76 | // size of the object exceeds the capacity of our stack. 77 | &mut heap, 78 | ); 79 | // Finally, we get the `Future`. Now poll it to obtain the output! 80 | let content = fut.await; 81 | // ... 82 | } 83 | ``` 84 | 85 | ## Why not async-trait? 86 | 87 | [async-trait](https://crates.io/crates/async-trait) is the most popular crate for the aforementioned 88 | problem. However, it may not play well with limited environments such as kernels or embedded 89 | systems, as it transforms every `async fn()` into `fn() -> Box`, requiring heap 90 | allocation. dynify doesn't have such limitation, since you can decide where to place trait objects. 91 | Additionally, you can opt out of the `alloc` feature to completely turn off heap allocation. 92 | 93 | Furthermore, dynify offers some unique features compared to async-trait. One of them, as shown in 94 | the example below, is the ability to reuse buffers across different trait objects: 95 | 96 | ```rust 97 | # use dynify::{from_fn, Dynify, Fn, PinDynify}; 98 | # use std::future::Future; 99 | # use std::mem::MaybeUninit; 100 | # use std::pin::Pin; 101 | trait Stream { 102 | type Item; 103 | async fn next(&mut self) -> Option; 104 | } 105 | 106 | trait DynStream { 107 | type Item; 108 | fn next(&mut self) -> Fn!(&mut Self => dyn '_ + Future>); 109 | fn next_boxed(&mut self) -> Pin>>>; 110 | } 111 | 112 | async fn process_stream(stream: &mut dyn DynStream) { 113 | let mut stack = [MaybeUninit::::uninit(); 16]; 114 | let mut heap = Vec::>::new(); 115 | 116 | // With dynify, all items are stored in the same buffer. 117 | while let Some(item) = stream.next().init2(&mut stack, &mut heap).await { 118 | // ... 119 | } 120 | // While with async-trait, every item is stored in a unique `Box`. 121 | while let Some(item) = stream.next_boxed().await { 122 | // ... 123 | } 124 | } 125 | ``` 126 | 127 | Nevertheless, the differences can be rather trivial in many cases. If you don't have these concerns, 128 | it's better to go with the battle tested async-trait. 129 | 130 | ## Features 131 | 132 | - **alloc**: Enable container implementations for types that require heap allocation such as `Box` 133 | and `Vec`. 134 | - **smallvec**: Enable container implementations for `SmallVec`, a drop-in replacement for 135 | `[u8; N] + Vec`. 136 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("lib.md") ] 2 | #![cfg_attr(coverage_nightly, feature(coverage_attribute))] 3 | #![cfg_attr(docsrs, feature(doc_cfg))] 4 | #![cfg_attr(not(test), no_std)] 5 | #![allow(unsafe_op_in_unsafe_fn)] 6 | #![deny(clippy::unsound_collection_transmute)] 7 | 8 | #[cfg(feature = "alloc")] 9 | extern crate alloc; 10 | 11 | #[macro_use] 12 | mod utils; 13 | mod closure; 14 | mod constructor; 15 | mod container; 16 | mod function; 17 | mod receiver; 18 | 19 | #[doc(inline)] 20 | #[cfg(feature = "alloc")] 21 | pub use self::container::Boxed; 22 | #[doc(inline)] 23 | pub use self::{ 24 | closure::from_closure, 25 | constructor::{Construct, Dynify, Opaque, PinConstruct, PinDynify, Slot}, 26 | container::{Buffered, Emplace, OutOfCapacity, PinEmplace}, 27 | }; 28 | 29 | /// NON-PUBLIC API 30 | #[doc(hidden)] 31 | pub mod r#priv { 32 | pub use crate::function::{from_bare_fn, from_method, Fn}; 33 | #[cfg(feature = "alloc")] 34 | pub use crate::receiver::{ArcSelf, BoxSelf, RcSelf}; 35 | pub use crate::receiver::{Receiver, RefMutSelf, RefSelf}; 36 | 37 | pub type PinRefSelf<'a> = crate::receiver::Pin>; 38 | pub type PinRefMutSelf<'a> = crate::receiver::Pin>; 39 | #[cfg(feature = "alloc")] 40 | pub type PinBoxSelf = crate::receiver::Pin; 41 | #[cfg(feature = "alloc")] 42 | pub type PinRcSelf = crate::receiver::Pin; 43 | #[cfg(feature = "alloc")] 44 | pub type PinArcSelf = crate::receiver::Pin; 45 | } 46 | 47 | #[doc = include_str!("../README.md")] 48 | pub const _: () = {}; 49 | -------------------------------------------------------------------------------- /src/receiver.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::ptr::NonNull; 3 | 4 | use crate::utils::{Void, VoidPtr}; 5 | 6 | /// A utility trait used to erase the type of a method receiver. 7 | /// 8 | /// This trait is essential to enable a method to return a dyn compatible [`Fn`] 9 | /// constructor. 10 | /// 11 | /// # Safety 12 | /// 13 | /// The implementor must adhere the documented contracts of each method. 14 | /// 15 | /// [`Fn`]: crate::function::Fn 16 | pub unsafe trait Receiver: core::ops::Deref { 17 | /// The sealed type of this receiver. 18 | type Sealed; 19 | 20 | /// Erases the type of this method receiver and returns a sealed object. 21 | /// 22 | /// The returned value may not be passed to other methods even if they have 23 | /// the same type. 24 | fn seal(self) -> Self::Sealed; 25 | 26 | /// Recovers the original type from the sealed object. 27 | /// 28 | /// # Safety 29 | /// 30 | /// `sealed` must be created from the original receiver of the method to 31 | /// which it is passed. 32 | unsafe fn unseal(sealed: Self::Sealed) -> Self; 33 | } 34 | 35 | /// The sealed type of `Pin`. 36 | pub struct Pin(T); 37 | unsafe impl Receiver for core::pin::Pin { 38 | type Sealed = Pin; 39 | fn seal(self) -> Self::Sealed { 40 | unsafe { 41 | let this = core::pin::Pin::into_inner_unchecked(self); 42 | Pin(T::seal(this)) 43 | } 44 | } 45 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 46 | core::pin::Pin::new_unchecked(T::unseal(sealed.0)) 47 | } 48 | } 49 | 50 | /// The sealed type of `&Self`. 51 | pub struct RefSelf<'a>(VoidPtr, PhantomData<&'a Void>); 52 | unsafe impl<'a, T> Receiver for &'a T { 53 | type Sealed = RefSelf<'a>; 54 | fn seal(self) -> Self::Sealed { 55 | RefSelf(NonNull::from(self).cast(), PhantomData) 56 | } 57 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 58 | sealed.0.cast().as_ref() 59 | } 60 | } 61 | 62 | /// The sealed type of `&mut Self`. 63 | pub struct RefMutSelf<'a>(VoidPtr, PhantomData<&'a mut Void>); 64 | unsafe impl<'a, T> Receiver for &'a mut T { 65 | type Sealed = RefMutSelf<'a>; 66 | fn seal(self) -> Self::Sealed { 67 | RefMutSelf(NonNull::from(self).cast(), PhantomData) 68 | } 69 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 70 | sealed.0.cast().as_mut() 71 | } 72 | } 73 | 74 | #[cfg(feature = "alloc")] 75 | mod __alloc { 76 | use alloc::boxed::Box; 77 | use alloc::rc::Rc; 78 | use alloc::sync::Arc; 79 | 80 | use super::*; 81 | 82 | struct AllocReceiver { 83 | data: VoidPtr, 84 | drop_fn: unsafe fn(VoidPtr), 85 | } 86 | impl AllocReceiver { 87 | fn into_raw(self) -> VoidPtr { 88 | let data = self.data; 89 | core::mem::forget(self); 90 | data 91 | } 92 | } 93 | impl Drop for AllocReceiver { 94 | fn drop(&mut self) { 95 | unsafe { (self.drop_fn)(self.data) } 96 | } 97 | } 98 | 99 | /// The sealed type of `Box`. 100 | pub struct BoxSelf(AllocReceiver); 101 | unsafe impl Receiver for Box { 102 | type Sealed = BoxSelf; 103 | fn seal(self) -> Self::Sealed { 104 | unsafe fn drop_fn(data: VoidPtr) { 105 | drop(Box::from_raw(data.cast::().as_ptr())); 106 | } 107 | unsafe { 108 | BoxSelf(AllocReceiver { 109 | data: NonNull::new_unchecked(Box::into_raw(self)).cast(), 110 | drop_fn: drop_fn::, 111 | }) 112 | } 113 | } 114 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 115 | let data = sealed.0.into_raw(); 116 | Box::from_raw(data.cast().as_ptr()) 117 | } 118 | } 119 | 120 | /// The sealed type of `Rc`. 121 | pub struct RcSelf(AllocReceiver); 122 | unsafe impl Receiver for Rc { 123 | type Sealed = RcSelf; 124 | fn seal(self) -> Self::Sealed { 125 | unsafe fn drop_fn(data: VoidPtr) { 126 | drop(Rc::from_raw(data.cast::().as_ptr())); 127 | } 128 | unsafe { 129 | RcSelf(AllocReceiver { 130 | data: NonNull::new_unchecked(Rc::into_raw(self).cast_mut()).cast(), 131 | drop_fn: drop_fn::, 132 | }) 133 | } 134 | } 135 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 136 | let data = sealed.0.into_raw(); 137 | Rc::from_raw(data.cast().as_ptr()) 138 | } 139 | } 140 | 141 | /// The sealed type of `Arc`. 142 | pub struct ArcSelf(AllocReceiver); 143 | unsafe impl Receiver for Arc { 144 | type Sealed = ArcSelf; 145 | fn seal(self) -> Self::Sealed { 146 | unsafe fn drop_fn(data: VoidPtr) { 147 | drop(Arc::from_raw(data.cast::().as_ptr())); 148 | } 149 | unsafe { 150 | ArcSelf(AllocReceiver { 151 | data: NonNull::new_unchecked(Arc::into_raw(self).cast_mut()).cast(), 152 | drop_fn: drop_fn::, 153 | }) 154 | } 155 | } 156 | unsafe fn unseal(sealed: Self::Sealed) -> Self { 157 | let data = sealed.0.into_raw(); 158 | Arc::from_raw(data.cast().as_ptr()) 159 | } 160 | } 161 | } 162 | #[cfg(feature = "alloc")] 163 | pub use __alloc::*; 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use std::pin::Pin; 168 | use std::rc::Rc; 169 | use std::sync::Arc; 170 | 171 | use rstest::rstest; 172 | 173 | use super::*; 174 | use crate::utils::DropCounter; 175 | 176 | struct FakeSelf(i32); 177 | 178 | #[rstest] 179 | #[case(& FakeSelf(1))] 180 | #[case(&mut FakeSelf(2))] 181 | #[case(Box::new(FakeSelf(3)))] 182 | #[case(Rc::new(FakeSelf(4)))] 183 | #[case(Arc::new(FakeSelf(5)))] 184 | #[case(Pin::new(Box::new(FakeSelf(6))))] 185 | fn unsealed_ptr_matches_original(#[case] orig: R) 186 | where 187 | R: Receiver, 188 | { 189 | let orig_addr = std::ptr::from_ref(&*orig); 190 | let orig_val = orig.0; 191 | let sealed = orig.seal(); 192 | let curr = unsafe { R::unseal(sealed) }; 193 | let curr_addr = std::ptr::from_ref(&*curr); 194 | let curr_val = curr.0; 195 | assert_eq!(curr_addr, orig_addr); 196 | assert_eq!(curr_val, orig_val); 197 | } 198 | 199 | #[rstest] 200 | #[case(Box::new(DropCounter))] 201 | #[case(Rc::new(DropCounter))] 202 | #[case(Arc::new(DropCounter))] 203 | #[case(Pin::new(Rc::new(DropCounter)))] 204 | fn sealed_ptr_drop_works(#[case] recv: impl Receiver) { 205 | assert_eq!(DropCounter::count(), 0); 206 | drop(recv); 207 | assert_eq!(DropCounter::count(), 1); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::mem::ManuallyDrop; 4 | 5 | pub enum Void {} 6 | pub(crate) type VoidPtr = core::ptr::NonNull; 7 | 8 | /// Defines a macro with its internal rules hidden on rustdoc. 9 | macro_rules! doc_macro { 10 | ($(#[$attr:meta])* macro $name:ident $documented:tt $real:tt) => { 11 | #[cfg(doc)] $(#[$attr])* macro_rules! $name $documented 12 | #[cfg(not(doc))] $(#[$attr])* macro_rules! $name $real 13 | }; 14 | } 15 | 16 | pub(crate) struct Defer(ManuallyDrop); 17 | impl Drop for Defer { 18 | fn drop(&mut self) { 19 | unsafe { ManuallyDrop::take(&mut self.0)() } 20 | } 21 | } 22 | /// Registers callbacks when exiting the current scope. 23 | pub(crate) fn defer(f: F) -> Defer { 24 | Defer(ManuallyDrop::new(f)) 25 | } 26 | 27 | #[allow(clippy::items_after_test_module)] 28 | #[cfg(test)] 29 | mod test_utils { 30 | use core::mem::MaybeUninit; 31 | use std::any::Any; 32 | use std::cell::Cell; 33 | use std::fmt; 34 | use std::future::Future; 35 | use std::ops::RangeBounds; 36 | 37 | pub(crate) trait DebugAny: Any + fmt::Debug {} 38 | impl DebugAny for T {} 39 | 40 | pub(crate) type StrFut<'a> = dyn 'a + Future; 41 | pub(crate) type OpqStrFut<'a> = crate::Opaque>; 42 | pub(crate) type OpqAny<'a> = crate::Opaque; 43 | 44 | /// A thread-local counter that increments when it gets dropped. 45 | pub(crate) struct DropCounter; 46 | thread_local! { 47 | static COUNT: Cell = const { Cell::new(0) }; 48 | } 49 | impl Drop for DropCounter { 50 | fn drop(&mut self) { 51 | COUNT.set(COUNT.get() + 1); 52 | } 53 | } 54 | impl DropCounter { 55 | pub fn count() -> usize { 56 | COUNT.get() 57 | } 58 | } 59 | 60 | pub(crate) fn randarr() -> [u8; N] { 61 | let mut arr = [0; N]; 62 | arr.fill_with(|| fastrand::alphanumeric() as u32 as u8); 63 | arr 64 | } 65 | 66 | pub(crate) fn newstk() -> [MaybeUninit; N] { 67 | [MaybeUninit::uninit(); N] 68 | } 69 | 70 | pub(crate) fn randstr(len: impl RangeBounds) -> String { 71 | std::iter::repeat_with(fastrand::alphanumeric) 72 | .take(fastrand::usize(len)) 73 | .collect() 74 | } 75 | 76 | pub(crate) fn newheap(len: usize) -> Vec> { 77 | vec![MaybeUninit::uninit(); len] 78 | } 79 | 80 | pub(crate) fn newheap_fixed(len: usize) -> Box<[MaybeUninit]> { 81 | newheap(len).into_boxed_slice() 82 | } 83 | } 84 | #[cfg(test)] 85 | pub(crate) use test_utils::*; 86 | -------------------------------------------------------------------------------- /tests/ui.rs: -------------------------------------------------------------------------------- 1 | /// Compile tests. 2 | #[test] 3 | #[cfg_attr(any(miri, coverage), ignore)] // compile tests are meaningless for coverage 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*_fail.rs"); 7 | t.pass("tests/ui/*_pass.rs"); 8 | } 9 | -------------------------------------------------------------------------------- /tests/ui/buffered_mut_borrow_twice_fail.rs: -------------------------------------------------------------------------------- 1 | use dynify::{from_closure, Buffered, Dynify}; 2 | 3 | // `Buffered` holds mutable reference exclusively 4 | fn main() { 5 | let mut stack = std::mem::MaybeUninit::<[u8; 16]>::uninit(); 6 | let init1 = from_closure(|slot| slot.write(123)); 7 | let init2 = from_closure(|slot| slot.write(456)); 8 | 9 | let val1: Buffered = init1.init(&mut stack); 10 | let val2: Buffered = init2.init(&mut stack); // fails 11 | drop((val1, val2)); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/buffered_mut_borrow_twice_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0499]: cannot borrow `stack` as mutable more than once at a time 2 | --> tests/ui/buffered_mut_borrow_twice_fail.rs:10:42 3 | | 4 | 9 | let val1: Buffered = init1.init(&mut stack); 5 | | ---------- first mutable borrow occurs here 6 | 10 | let val2: Buffered = init2.init(&mut stack); // fails 7 | | ^^^^^^^^^^ second mutable borrow occurs here 8 | 11 | drop((val1, val2)); 9 | | ---- first borrow later used here 10 | -------------------------------------------------------------------------------- /tests/ui/from_closure_coercion_pass.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | fn main() { 4 | let var = String::from("abc"); 5 | let _ = dynify::from_closure(move |slot| slot.write(var) as &mut dynify::Opaque); 6 | } 7 | -------------------------------------------------------------------------------- /tests/ui/from_closure_leak_slot_fail.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut slot_leaked = None; 3 | let _ = dynify::from_closure::<(), (), _>(|slot| { 4 | slot_leaked = Some(slot); 5 | unreachable!(); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /tests/ui/from_closure_leak_slot_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0521]: borrowed data escapes outside of closure 2 | --> tests/ui/from_closure_leak_slot_fail.rs:4:9 3 | | 4 | 2 | let mut slot_leaked = None; 5 | | --------------- `slot_leaked` declared here, outside of the closure body 6 | 3 | let _ = dynify::from_closure::<(), (), _>(|slot| { 7 | | ---- `slot` is a reference that is only valid in the closure body 8 | 4 | slot_leaked = Some(slot); 9 | | ^^^^^^^^^^^^^^^^^^^^^^^^ `slot` escapes the closure body here 10 | -------------------------------------------------------------------------------- /tests/ui/from_closure_wrong_type_fail.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = dynify::from_closure(|slot| slot.write(123i32) as &mut dynify::Opaque); 3 | } 4 | -------------------------------------------------------------------------------- /tests/ui/from_closure_wrong_type_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0605]: non-primitive cast: `&mut Opaque` as `&mut Opaque` 2 | --> tests/ui/from_closure_wrong_type_fail.rs:2:41 3 | | 4 | 2 | let _ = dynify::from_closure(|slot| slot.write(123i32) as &mut dynify::Opaque); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object 6 | -------------------------------------------------------------------------------- /tests/ui/from_fn_mut_borrow_arg_twice_fail.rs: -------------------------------------------------------------------------------- 1 | use dynify::{from_fn, Fn}; 2 | 3 | struct Test; 4 | impl Test { 5 | fn test<'a, 'b>(&'a self, data: &'b mut [u8]) -> Fn!(&'a Self, &'b mut [u8] => ()) { 6 | from_fn!(|_: &Self, _: &mut [u8]| {}, self, data) 7 | } 8 | } 9 | 10 | // `from_fn` holds mutable reference exclusively 11 | fn main() { 12 | let test = Test; 13 | let mut data = [0u8; 4]; 14 | 15 | let init1 = test.test(&mut data); 16 | let init2 = test.test(&mut data); // fails 17 | drop((init1, init2)); 18 | } 19 | -------------------------------------------------------------------------------- /tests/ui/from_fn_mut_borrow_arg_twice_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0499]: cannot borrow `data` as mutable more than once at a time 2 | --> tests/ui/from_fn_mut_borrow_arg_twice_fail.rs:16:27 3 | | 4 | 15 | let init1 = test.test(&mut data); 5 | | --------- first mutable borrow occurs here 6 | 16 | let init2 = test.test(&mut data); // fails 7 | | ^^^^^^^^^ second mutable borrow occurs here 8 | 17 | drop((init1, init2)); 9 | | ----- first borrow later used here 10 | -------------------------------------------------------------------------------- /tests/ui/from_fn_mut_borrow_self_twice_fail.rs: -------------------------------------------------------------------------------- 1 | use dynify::{from_fn, Fn}; 2 | 3 | struct Test; 4 | impl Test { 5 | fn test(&mut self) -> Fn!(&mut Self => ()) { 6 | from_fn!(|_: &mut Self| {}, self) 7 | } 8 | } 9 | 10 | // `from_fn` holds mutable reference exclusively 11 | fn main() { 12 | let mut test = Test; 13 | 14 | let init1 = test.test(); 15 | let init2 = test.test(); // fails 16 | drop((init1, init2)); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/from_fn_mut_borrow_self_twice_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0499]: cannot borrow `test` as mutable more than once at a time 2 | --> tests/ui/from_fn_mut_borrow_self_twice_fail.rs:15:17 3 | | 4 | 14 | let init1 = test.test(); 5 | | ---- first mutable borrow occurs here 6 | 15 | let init2 = test.test(); // fails 7 | | ^^^^ second mutable borrow occurs here 8 | 16 | drop((init1, init2)); 9 | | ----- first borrow later used here 10 | -------------------------------------------------------------------------------- /tests/ui/from_fn_two_diff_mut_ref_pass.rs: -------------------------------------------------------------------------------- 1 | use dynify::{from_fn, Dynify, Fn}; 2 | 3 | struct Test; 4 | impl Test { 5 | fn test<'a, 'b>( 6 | &'a mut self, 7 | data: &'b mut [u8], 8 | ) -> Fn!(&'a mut Self, &'b mut [u8] => dyn 'b + Send) { 9 | from_fn!(|_: &mut Self, d: &'b mut [u8]| d, self, data) 10 | } 11 | } 12 | 13 | // `from_fn` holds mutable reference exclusively 14 | fn main() { 15 | let mut test = Test; 16 | let mut data1 = [0u8; 4]; 17 | let mut data2 = [0u8; 4]; 18 | 19 | let obj1 = test.test(&mut data1).boxed(); 20 | let obj2 = test.test(&mut data2).boxed(); 21 | drop((obj1, obj2)); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/from_fn_with_closure_fail.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let var = 123; 3 | dynify::from_fn!(move || var); 4 | } 5 | -------------------------------------------------------------------------------- /tests/ui/from_fn_with_closure_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: arguments to this function are incorrect 2 | --> tests/ui/from_fn_with_closure_fail.rs:3:5 3 | | 4 | 3 | dynify::from_fn!(move || var); 5 | | ^^^^^^^^^^^^^^^^^-------^^^^^ 6 | | | 7 | | the expected closure 8 | | the found closure 9 | | 10 | note: expected fn pointer, found closure 11 | --> tests/ui/from_fn_with_closure_fail.rs:3:5 12 | | 13 | 3 | dynify::from_fn!(move || var); 14 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | = note: expected fn pointer `fn(dynify::function::MustNotBeClosure) -> {closure@$DIR/tests/ui/from_fn_with_closure_fail.rs:3:22: 3:29}` 16 | found closure `{closure@$DIR/src/function.rs:187:17: 187:18}` 17 | note: closures can only be coerced to `fn` types if they do not capture any variables 18 | --> tests/ui/from_fn_with_closure_fail.rs:3:30 19 | | 20 | 3 | dynify::from_fn!(move || var); 21 | | ^^^ `var` captured here 22 | note: expected fn pointer, found closure 23 | --> tests/ui/from_fn_with_closure_fail.rs:3:5 24 | | 25 | 3 | dynify::from_fn!(move || var); 26 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 | = note: expected fn pointer `for<'a> unsafe fn(Slot<'a>, ()) -> &'a mut Opaque<_>` 28 | found closure `{closure@$DIR/src/function.rs:189:17: 189:37}` 29 | note: closures can only be coerced to `fn` types if they do not capture any variables 30 | --> tests/ui/from_fn_with_closure_fail.rs:3:30 31 | | 32 | 3 | dynify::from_fn!(move || var); 33 | | ^^^ `var` captured here 34 | note: function defined here 35 | --> src/function.rs 36 | | 37 | | pub unsafe fn from_bare_fn( 38 | | ^^^^^^^^^^^^ 39 | = note: this error originates in the macro `$crate::__from_fn` which comes from the expansion of the macro `dynify::from_fn` (in Nightly builds, run with -Z macro-backtrace for more info) 40 | -------------------------------------------------------------------------------- /tests/ui/from_fn_with_wrong_type_fail.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _: dynify::Fn!(=> u32) = dynify::from_fn!(|| 123i32); 3 | } 4 | -------------------------------------------------------------------------------- /tests/ui/from_fn_with_wrong_type_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0605]: non-primitive cast: `&mut Opaque` as `&mut Opaque` 2 | --> tests/ui/from_fn_with_wrong_type_fail.rs:2:34 3 | | 4 | 2 | let _: dynify::Fn!(=> u32) = dynify::from_fn!(|| 123i32); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object 6 | | 7 | = note: this error originates in the macro `$crate::__from_fn` which comes from the expansion of the macro `dynify::from_fn` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/ui/pinned_buffered_unpin_fail.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomPinned; 2 | use std::pin::Pin; 3 | 4 | use dynify::{from_closure, Buffered, Dynify}; 5 | 6 | // Wraps Pin::into_inner to prevent rustc from reporting errors from rust-src. 7 | fn unpin(ptr: Pin

) -> P 8 | where 9 | P::Target: Unpin, 10 | { 11 | std::pin::Pin::into_inner(ptr) 12 | } 13 | 14 | fn main() { 15 | let mut stack = std::mem::MaybeUninit::<[u8; 16]>::uninit(); 16 | let init = from_closure(|slot| slot.write(PhantomPinned)); 17 | let val: Buffered = init.init(&mut stack); 18 | 19 | let pinned = std::pin::pin!(val); 20 | let _ = unpin(pinned); // fails 21 | } 22 | -------------------------------------------------------------------------------- /tests/ui/pinned_buffered_unpin_fail.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `PhantomPinned` cannot be unpinned 2 | --> tests/ui/pinned_buffered_unpin_fail.rs:20:19 3 | | 4 | 20 | let _ = unpin(pinned); // fails 5 | | ----- ^^^^^^ the trait `Unpin` is not implemented for `Buffered<'_, PhantomPinned>` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = note: the trait bound `Buffered<'_, PhantomPinned>: Unpin` is not satisfied 10 | = note: required for `Buffered<'_, PhantomPinned>` to implement `Unpin` 11 | note: required by a bound in `unpin` 12 | --> tests/ui/pinned_buffered_unpin_fail.rs:9:16 13 | | 14 | 7 | fn unpin(ptr: Pin

) -> P 15 | | ----- required by a bound in this function 16 | 8 | where 17 | 9 | P::Target: Unpin, 18 | | ^^^^^ required by this bound in `unpin` 19 | help: consider borrowing here 20 | | 21 | 20 | let _ = unpin(&pinned); // fails 22 | | + 23 | 20 | let _ = unpin(&mut pinned); // fails 24 | | ++++ 25 | --------------------------------------------------------------------------------