├── .cargo └── config ├── .github └── workflows │ ├── CI.yml │ └── future-proof.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSE-ZLIB ├── README.md ├── cargo_publish.sh ├── change_version.sh ├── rust-toolchain.toml ├── src ├── compile_fail_tests.md ├── const.rs ├── lib.rs └── proc_macros │ ├── Cargo.toml │ ├── input_bytes.rs │ └── mod.rs └── tests ├── consts.rs └── main.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | # Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 2 | [alias] 3 | test-ui = ["test", "--doc", "--features", "ui-tests", "--", "--nocapture", "--test-threads", "1"] 4 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | # == CHECK == # 11 | check: 12 | name: "Check beta stable and MSRV=1.65.0" 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | rust-toolchains: 17 | - 1.65.0 18 | - stable 19 | - beta 20 | cargo-locked: ["--locked", ""] 21 | steps: 22 | - name: Install Rust toolchain 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: ${{ matrix.rust-toolchains }} 27 | override: true 28 | 29 | - name: Clone repo 30 | uses: actions/checkout@v2 31 | 32 | - name: Update `Cargo.lock` 33 | if: matrix.cargo-locked != '--locked' 34 | run: cargo update -v 35 | 36 | - name: Cargo check 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: check 40 | args: ${{ matrix.cargo-locked }} 41 | 42 | # == BUILD & TEST == # 43 | build-and-test: 44 | name: Build and test 45 | runs-on: ${{ matrix.os }} 46 | needs: [check] 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | os: 51 | - ubuntu-latest 52 | - macos-latest 53 | - windows-latest 54 | rust-toolchains: 55 | - 1.65.0 56 | - stable 57 | steps: 58 | - name: Install Rust toolchain 59 | uses: actions-rs/toolchain@v1 60 | with: 61 | profile: default 62 | override: true 63 | toolchain: ${{ matrix.rust-toolchains }} 64 | 65 | - name: Clone repo 66 | uses: actions/checkout@v2 67 | 68 | - name: Cargo test 69 | uses: actions-rs/cargo@v1 70 | with: 71 | command: test 72 | 73 | - name: Cargo test (embedded doc tests) 74 | if: matrix.rust-toolchains == 'stable' 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: test 78 | args: --features better-docs --doc 79 | 80 | # == UI TESTS == 81 | ui-test: 82 | name: UI Tests 83 | runs-on: ubuntu-latest 84 | needs: [check] 85 | steps: 86 | - name: Install Rust toolchain 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | profile: default 90 | override: true 91 | toolchain: stable 92 | 93 | - name: Clone repo 94 | uses: actions/checkout@v2 95 | 96 | - name: Cargo UI test 97 | uses: actions-rs/cargo@v1 98 | with: 99 | command: test-ui 100 | -------------------------------------------------------------------------------- /.github/workflows/future-proof.yml: -------------------------------------------------------------------------------- 1 | # Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 2 | name: Cron CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '0 8 * * 1,5' 10 | 11 | jobs: 12 | # == TEST == # 13 | test-no-ui: 14 | name: (Check & Build &) Test 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: 20 | - ubuntu-latest 21 | - macos-latest 22 | - windows-latest 23 | rust-toolchains: 24 | - 1.65.0 25 | - stable 26 | - beta 27 | - nightly 28 | cargo-locked: ["--locked", ""] 29 | steps: 30 | - name: Install Rust toolchain 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | profile: default 34 | override: true 35 | toolchain: ${{ matrix.rust-toolchains }} 36 | 37 | - name: Clone repo 38 | uses: actions/checkout@v2 39 | 40 | - name: Update `Cargo.lock` 41 | if: matrix.cargo-locked != '--locked' 42 | run: cargo update -v 43 | 44 | - name: Cargo test 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | args: ${{ matrix.cargo-locked }} 49 | 50 | - name: Cargo test (embed `README.md` + UI) 51 | if: matrix.rust-toolchains != '1.65.0' 52 | uses: actions-rs/cargo@v1 53 | with: 54 | command: test-ui 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /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 = "byte-strings" 7 | version = "0.3.1" 8 | dependencies = [ 9 | "byte-strings-proc_macros", 10 | ] 11 | 12 | [[package]] 13 | name = "byte-strings-proc_macros" 14 | version = "0.3.1" 15 | dependencies = [ 16 | "proc-macro2", 17 | "quote", 18 | "syn", 19 | ] 20 | 21 | [[package]] 22 | name = "proc-macro2" 23 | version = "1.0.58" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" 26 | dependencies = [ 27 | "unicode-ident", 28 | ] 29 | 30 | [[package]] 31 | name = "quote" 32 | version = "1.0.27" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 35 | dependencies = [ 36 | "proc-macro2", 37 | ] 38 | 39 | [[package]] 40 | name = "syn" 41 | version = "2.0.16" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 44 | dependencies = [ 45 | "proc-macro2", 46 | "quote", 47 | "unicode-ident", 48 | ] 49 | 50 | [[package]] 51 | name = "unicode-ident" 52 | version = "1.0.8" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 2 | [package] 3 | name = "byte-strings" 4 | authors = [ 5 | "Daniel Henry-Mantilla ", 6 | ] 7 | version = "0.3.1" # Keep in sync 8 | edition = "2018" 9 | rust-version = "1.65.0" 10 | 11 | license = "Zlib OR MIT OR Apache-2.0" 12 | repository = "https://github.com/danielhenrymantilla/byte-strings-rs" 13 | documentation = "https://docs.rs/byte-strings" 14 | readme = "README.md" 15 | 16 | description = "Rust byte strings manipulation, for a better and safer C FFI" 17 | keywords = ["ffi", "c", "string", "char", "c_str"] 18 | 19 | categories = ["api-bindings", "rust-patterns"] 20 | 21 | [features] 22 | better-docs = [] # allowed to break MSRV 23 | ui-tests = ["better-docs"] 24 | const-friendly = [] # Deprecated: now enabled by default. 25 | 26 | [dependencies] 27 | 28 | [dependencies.byte-strings-proc_macros] 29 | path = "src/proc_macros" 30 | version = "=0.3.1" # Keep in sync 31 | 32 | [dev-dependencies] 33 | 34 | [workspace] 35 | members = [ 36 | "src/proc_macros", 37 | ] 38 | 39 | [package.metadata.docs.rs] 40 | all-features = true 41 | -------------------------------------------------------------------------------- /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 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Daniel Henry-Mantilla 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Henry-Mantilla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-ZLIB: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | (C) 2021 Daniel Henry-Mantilla 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `::byte-strings` 2 | 3 | Rust zero-cost byte strings manipulation, for a better and safer FFI 4 | 5 | [![Repository](https://img.shields.io/badge/repository-GitHub-brightgreen.svg)]( 6 | https://github.com/danielhenrymantilla/byte-strings-rs) 7 | [![Latest version](https://img.shields.io/crates/v/byte-strings.svg)]( 8 | https://crates.io/crates/byte-strings) 9 | [![Documentation](https://docs.rs/byte-strings/badge.svg)]( 10 | https://docs.rs/byte-strings) 11 | [![MSRV](https://img.shields.io/badge/MSRV-1.65.0-white)]( 12 | https://gist.github.com/danielhenrymantilla/8e5b721b3929084562f8f65668920c33) 13 | [![License](https://img.shields.io/crates/l/byte-strings.svg)]( 14 | https://github.com/danielhenrymantilla/byte-strings-rs/blob/master/LICENSE-ZLIB) 15 | [![CI](https://github.com/danielhenrymantilla/byte-strings-rs/workflows/CI/badge.svg)]( 16 | https://github.com/danielhenrymantilla/byte-strings-rs/actions) 17 | 18 | 19 | 20 | ## Example 21 | 22 | Featuring the `c_str!` macro to create **valid C string literals** with 23 | literally no runtime cost! 24 | 25 | ```rust 26 | #[macro_use] 27 | extern crate byte_strings; 28 | 29 | /// Some lib 30 | mod safe { 31 | use ::std::{ 32 | ffi::CStr, 33 | os::raw::{c_char, c_int}, 34 | }; 35 | 36 | /// private unsafe C FFI 37 | mod ffi { 38 | use super::*; 39 | 40 | extern "C" { 41 | pub 42 | fn puts (_: *const c_char) 43 | -> c_int 44 | ; 45 | } 46 | } 47 | 48 | /// lib API: safe Rust wrapper => uses `CStr` 49 | pub 50 | fn puts (message: &'_ CStr) 51 | -> i32 52 | { 53 | unsafe { 54 | ffi::puts(message.as_ptr()) as i32 55 | } 56 | } 57 | } 58 | 59 | fn main () 60 | { 61 | safe::puts(c!("Hello, World!")); 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /cargo_publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euxo pipefail 4 | 5 | (cd src/proc_macros 6 | cargo +stable publish 7 | ) 8 | 9 | for i in $(seq 10) 10 | do 11 | cargo +stable publish && break 12 | sleep 5 13 | done 14 | -------------------------------------------------------------------------------- /change_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euxo pipefail 4 | 5 | find . \ 6 | -type f \ 7 | -name 'Cargo.toml' \ 8 | -print \ 9 | -a \ 10 | -exec \ 11 | sed -i -E "s/\"(=)?.*\"( # Keep in sync)/\"\\1$1\"\\2/g" '{}' \ 12 | \; 13 | 14 | cargo +stable update -v -w 15 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = '1.65.0' 3 | # Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 4 | -------------------------------------------------------------------------------- /src/compile_fail_tests.md: -------------------------------------------------------------------------------- 1 | # The following snippets fail to compile 2 | 3 | ## Inner null is denied at compile-time 4 | 5 | ```rust ,compile_fail 6 | use ::byte_strings::*; 7 | 8 | let _ = c!("Hell\0, World!"); 9 | ``` 10 | 11 | ## `const_concat!` rejects byte strings 12 | 13 | ```rust ,compile_fail 14 | 15 | use ::byte_strings::*; 16 | 17 | let _ = const_::concat!(b"\xff\xff"); 18 | let _ = const_::concat!(b"Hi"); 19 | ``` 20 | -------------------------------------------------------------------------------- /src/const.rs: -------------------------------------------------------------------------------- 1 | /*! `const`-friendly equivalents of the top-level crates. 2 | 3 |
const-friendly? 4 | 5 | The default / naïve implementation of the macros of this crate (ab)used the 6 | proc-macro capabilities to inspect the contents / values of a given (byte) 7 | string literal. But besides that capability, (procedural) macros can't do any 8 | other form of semantic evaluation. 9 | 10 | This means that when fed a `const`ant expression evaluating to a valid (byte) 11 | string literal, 12 | 13 | - such as a `const`: 14 | 15 | ```rust ,compile_fail 16 | #[macro_use] 17 | extern crate byte_strings; 18 | 19 | fn main () 20 | { 21 | const FOO: &str = "foo"; 22 | 23 | // FAILS with something along the lines of "expected a literal" 24 | const FOO_BYTES: &[u8] = as_bytes!(FOO); 25 | } 26 | ``` 27 | 28 | - or some macro (besides `concat!`, `stringify!`, which are 29 | *syntactically* detected and thus get to feature ad-hoc polyfilled 30 | support): 31 | 32 | ```rust ,compile_fail 33 | #[macro_use] 34 | extern crate byte_strings; 35 | 36 | # mod third_party_lib { pub(in super) use stringify as their_macro; } 37 | # 38 | fn main () 39 | { 40 | // FAILS with something along the lines of "expected a literal" 41 | let _ = as_bytes!(third_party_lib::their_macro!(...)); 42 | } 43 | ``` 44 | 45 |
46 | 47 | The macros of this module have been written to use `const fn`s as much as 48 | possible, so as to use the **semantic evaluation built within the compiler** 49 | rather than the limited syntactical evaluation of classic macros. This allows 50 | the macros in this module to be able to support any kind of `const`-evaluatable 51 | (byte) string: 52 | 53 | ```rust 54 | use ::byte_strings::const_::c_str; 55 | use ::core::ffi::CStr; 56 | 57 | const MESSAGE: &str = "Hello, World!"; 58 | const C_MESSAGE: &CStr = c_str!(MESSAGE); // OK! 59 | ``` 60 | */ 61 | 62 | #[doc(hidden)] /** Not part of the public API **/ pub 63 | mod __ { 64 | #![allow(nonstandard_style)] 65 | pub use ::core; 66 | 67 | pub 68 | struct const_ /* = */ ( 69 | pub T, 70 | ); 71 | 72 | impl<'lt> const_<&'lt str> { 73 | pub 74 | const 75 | fn as_bytes (self) 76 | -> &'lt [u8] 77 | { 78 | self.0.as_bytes() 79 | } 80 | } 81 | 82 | impl<'lt> const_<&'lt [u8]> { 83 | pub 84 | const 85 | fn as_bytes (self) 86 | -> &'lt [u8] 87 | { 88 | self.0 89 | } 90 | } 91 | 92 | impl<'lt, const N: usize> const_<&'lt [u8; N]> { 93 | pub 94 | const 95 | fn as_bytes (self) 96 | -> &'lt [u8] 97 | { 98 | self.0 99 | } 100 | } 101 | 102 | #[repr(C)] 103 | pub 104 | struct Contiguous<_0, _1> /* = */ ( 105 | pub _0, 106 | pub _1, 107 | ); 108 | 109 | pub 110 | const 111 | fn c_strlen (bytes: &[u8]) 112 | -> usize 113 | { 114 | let mut i = 0; 115 | while i < bytes.len() { 116 | if bytes[i] == b'\0' { 117 | break; 118 | } 119 | i += 1; 120 | } 121 | i 122 | } 123 | 124 | pub 125 | struct c_strlen {} 126 | } 127 | 128 | /// [`const`-friendly][crate::const_] version of [`as_bytes!`][crate::as_bytes]. 129 | /// 130 | /// ```rust 131 | /// # fn main () {} 132 | /// #[macro_use] 133 | /// extern crate byte_strings; 134 | /// 135 | /// const MESSAGE: &str = "Hello, World!"; 136 | /// const MESSAGE_BYTES: &[u8] = const_as_bytes!(MESSAGE); 137 | /// ``` 138 | #[macro_export] 139 | macro_rules! const_as_bytes {( $s:expr $(,)? ) => ({ 140 | const __BYTES: &'static [u8] = $crate::const_::__::const_($s).as_bytes(); 141 | unsafe { 142 | $crate::__::core::mem::transmute::< 143 | *const u8, 144 | &'static [u8; __BYTES.len()], 145 | >( 146 | __BYTES.as_ptr() 147 | ) 148 | } 149 | })} 150 | #[doc(inline)] pub use const_as_bytes as as_bytes; 151 | 152 | /// [`const`-friendly][crate::const_] version of [`concat!`][::core::concat]. 153 | /// 154 | /// ```rust 155 | /// # fn main () {} 156 | /// #[macro_use] 157 | /// extern crate byte_strings; 158 | /// 159 | /// const GREETING: &str = "Hello"; 160 | /// const MESSAGE: &str = const_concat!(GREETING, ", World!"); 161 | /// ``` 162 | #[macro_export] 163 | macro_rules! const_concat {( 164 | $($s:expr),* $(,)? 165 | ) => ( 166 | unsafe { 167 | $crate::__::core::str::from_utf8_unchecked( 168 | $crate::const_concat_bytes!( 169 | $( 170 | <$crate::__::core::primitive::str>::as_bytes($s) 171 | ),* 172 | ) 173 | ) 174 | } 175 | )} 176 | #[doc(inline)] pub use const_concat as concat; 177 | 178 | /// [`const`-friendly][crate::const_] version of 179 | /// [`c_str!`][crate::c_str]. 180 | /// 181 | /// ```rust 182 | /// use ::byte_strings::const_; 183 | /// use ::core::ffi::CStr; 184 | /// 185 | /// const MESSAGE: &str = "Hello, World!"; 186 | /// const C_MESSAGE: &CStr = const_::c_str!(MESSAGE); 187 | /// ``` 188 | /// 189 | /// Inner null bytes are still rejected at compile time: 190 | /// 191 | /// ```rust ,compile_fail 192 | /// use ::byte_strings::const_; 193 | /// use ::core::ffi::CStr; 194 | /// 195 | /// const MESSAGE: &str = "Hell\0, World!"; 196 | /// const C_MESSAGE: &CStr = const_::c_str!(MESSAGE); // Error. 197 | /// ``` 198 | #[macro_export] 199 | macro_rules! const_cstr {() => ( $crate::cstr!() ); ( 200 | $($s:expr),* $(,)? 201 | ) => ({ 202 | const BYTES: &[$crate::__::core::primitive::u8] = { 203 | $crate::const_concat_bytes!($($s ,)*) 204 | }; 205 | /// Assert lack of inner null bytes. 206 | const _: $crate::const_::__::c_strlen::<{ 207 | BYTES.len() - if BYTES[BYTES.len() - 1] == b'\0' { 1 } else { 0 } 208 | }> = $crate::const_::__::c_strlen::<{ 209 | $crate::const_::__::c_strlen(BYTES) 210 | }> {}; 211 | unsafe { 212 | $crate::__::core::ffi::CStr::from_bytes_with_nul_unchecked( 213 | // Append a null terminator if needed. 214 | if BYTES[BYTES.len() - 1] == b'\0' { 215 | BYTES 216 | } else { 217 | $crate::const_concat_bytes!(BYTES, b"\0") 218 | } 219 | ) 220 | } 221 | })} 222 | #[doc(inline)] pub use const_cstr as c_str; 223 | 224 | /// [`const`-friendly][crate::const_] version of 225 | /// [`concat_bytes!`][crate::concat_bytes]. 226 | /// 227 | /// ```rust 228 | /// # fn main () {} 229 | /// #[macro_use] 230 | /// extern crate byte_strings; 231 | /// 232 | /// const GREETING: &str = "Hello"; 233 | /// const MESSAGE: &[u8; 13] = const_concat_bytes!(GREETING, ", World!"); 234 | /// ``` 235 | #[macro_export] 236 | macro_rules! const_concat_bytes { 237 | () => (b""); 238 | ( 239 | $single:expr $(,)? 240 | ) => ( 241 | $crate::const_as_bytes!($single) 242 | ); 243 | 244 | ( 245 | $first:expr $(, 246 | $rest:expr)+ $(,)? 247 | ) => ( 248 | $crate::__concat_bytes_two!( 249 | $crate::const_as_bytes!($first), 250 | $crate::const_concat_bytes!($($rest),+), 251 | ) 252 | ); 253 | } 254 | #[doc(inline)] pub use const_concat_bytes as concat_bytes; 255 | 256 | #[doc(hidden)] /** Not part of the public API */ #[macro_export] 257 | macro_rules! __concat_bytes_two {( 258 | $left:expr, 259 | $right:expr $(,)? 260 | ) => ({ 261 | const LEFT: &'static [$crate::__::core::primitive::u8] = $left; 262 | const RIGHT: &'static [$crate::__::core::primitive::u8] = $right; 263 | unsafe { 264 | use $crate::const_::__::{Contiguous, core::{self, primitive::*, mem}}; 265 | const LEFT_LEN: usize = LEFT.len(); 266 | const LEFT_BYTES: &'static [u8; LEFT_LEN] = unsafe { 267 | mem::transmute(LEFT.as_ptr()) 268 | }; 269 | const RIGHT_LEN: usize = RIGHT.len(); 270 | const RIGHT_BYTES: &'static [u8; RIGHT_LEN] = unsafe { 271 | mem::transmute(RIGHT.as_ptr()) 272 | }; 273 | const CONCAT_CONTIGUOUS: ( 274 | &'static Contiguous< 275 | [u8; LEFT_LEN], 276 | [u8; RIGHT_LEN], 277 | > 278 | ) = &Contiguous( 279 | *LEFT_BYTES, 280 | *RIGHT_BYTES, 281 | ); 282 | const CONCAT_BYTES: &'static [u8; LEFT_LEN + RIGHT_LEN] = unsafe { 283 | mem::transmute(CONCAT_CONTIGUOUS) 284 | }; 285 | CONCAT_BYTES 286 | } 287 | })} 288 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![no_std] 3 | 4 | // Fix rendering of `
` within bulleted lists: 5 | // Credit for this marvelous hack go to: https://github.com/rust-lang/cargo/issues/331#issuecomment-479847157 6 | #![doc(html_favicon_url = "\"> 7 | 12 | ( 44 | $crate::__::concat_bytes!( 45 | [$crate] 46 | $($expr),* 47 | ) 48 | )} 49 | 50 | /// Converts into a valid [C string] at compile time (no runtime cost) 51 | /// 52 | /// This macro takes any number of comma-separated byte string literals, 53 | /// or string literals, 54 | /// and evaluates to (a static reference to) a [C string] 55 | /// made of all the bytes of the given literals concatenated left-to-right, 56 | /// **with an appended null byte terminator**. 57 | /// 58 | /// Hence the macro evaluates to the type `&'static ::core::ffi::CStr`. 59 | /// 60 | /// # Example 61 | /// 62 | /// ```rust,edition2018 63 | /// use ::byte_strings::c_str; 64 | /// 65 | /// assert_eq!( 66 | /// c_str!("Hello, ", "World!"), 67 | /// ::std::ffi::CStr::from_bytes_with_nul(b"Hello, World!\0").unwrap(), 68 | /// ) 69 | /// ``` 70 | /// 71 | /// # Compilation error 72 | /// 73 | /// For the [C string] to be what should be expected, 74 | /// **the arguments cannot contain any null byte**. 75 | /// Else the compilation will fail. 76 | /// 77 | /// # Counter example 78 | /// 79 | /// ```rust,compile_fail 80 | /// # use ::byte_strings::c_str; 81 | /// // error: input literals cannot contain null bytes 82 | /// let hello_w = c_str!("Hello, ", "W\0rld!"); 83 | /// ``` 84 | /// 85 | /// ### Macro expansion: 86 | /// 87 | /// ```rust 88 | /// const _: &str = stringify! { 89 | /// c_str!("Hello, ", "World!") 90 | /// # }; 91 | /// ``` 92 | /// 93 | /// expands to 94 | /// 95 | /// ```rust 96 | /// unsafe { 97 | /// ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"Hello, World!\0") 98 | /// } 99 | /// # ; 100 | /// ``` 101 | #[macro_export] 102 | macro_rules! c_str {( 103 | $($literal:expr),* $(,)? 104 | ) => ( 105 | $crate::__::c_str!( 106 | [$crate] 107 | $($literal),* 108 | ) 109 | )} 110 | 111 | /// Shorthand alias. 112 | pub use c_str as c; 113 | 114 | // macro internals 115 | #[doc(hidden)] /** Not part of the public API */ pub 116 | mod __ { 117 | pub use { 118 | ::byte_strings_proc_macros::*, 119 | ::core, 120 | }; 121 | } 122 | 123 | #[path = "const.rs"] 124 | pub mod const_; 125 | 126 | #[cfg_attr(feature = "ui-tests", 127 | cfg_attr(all(), doc = include_str!("compile_fail_tests.md")), 128 | )] 129 | mod _compile_fail_tests {} 130 | -------------------------------------------------------------------------------- /src/proc_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 2 | [lib] 3 | proc-macro = true 4 | path = "mod.rs" 5 | 6 | [package] 7 | name = "byte-strings-proc_macros" 8 | authors = [ 9 | "Daniel Henry-Mantilla ", 10 | ] 11 | version = "0.3.1" # Keep in sync 12 | edition = "2018" 13 | 14 | license = "Zlib OR MIT OR Apache-2.0" 15 | repository = "https://github.com/danielhenrymantilla/byte-strings-rs" 16 | documentation = "https://docs.rs/byte-strings" 17 | 18 | description = "Internal: proc-macro backend of ::byte_strings." 19 | 20 | [dependencies] 21 | proc-macro2.version = "1.0.0" 22 | quote.version = "1.0.0" 23 | syn.version = "2.0.0" 24 | syn.features = [ 25 | "full", 26 | ] 27 | -------------------------------------------------------------------------------- /src/proc_macros/input_bytes.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Same as [`input`], but for an expect `($crate)` initial parameter 4 | /// to be robust to crate re-exports. 5 | pub(in crate) 6 | struct Input /* = */ ( 7 | pub(in crate) TokenStream2, 8 | pub(in crate) Vec, 9 | ); 10 | 11 | impl Parse for Input { 12 | fn parse (input: ParseStream<'_>) 13 | -> Result 14 | { 15 | let crate_; bracketed!(crate_ in input); 16 | Ok(Input(crate_.parse().unwrap(), input.parse::()?.0)) 17 | } 18 | } 19 | 20 | /// A sequence of: 21 | /// - (byte) string literals; 22 | /// 23 | /// - or macro invocations of `as_bytes/c{,_str}/concat{,_bytes}!` 24 | /// which are recursively fed such a sequence; 25 | /// 26 | /// - or `stringify!` invocations. 27 | struct InputBytes /* = */ ( 28 | Vec, 29 | ); 30 | 31 | impl Parse for InputBytes { 32 | fn parse (input: ParseStream<'_>) 33 | -> Result 34 | { 35 | macro_rules! supported_macros {( 36 | $($macro:ident),* $(,)? 37 | ) => ( 38 | mod kw { 39 | $( 40 | ::syn::custom_keyword!($macro); 41 | )* 42 | ::syn::custom_keyword!(stringify); 43 | } 44 | let mut ret = vec![0_u8; 0]; 45 | while input.is_empty().not() { 46 | let snoopy = input.lookahead1(); 47 | match () { 48 | | _case if snoopy.peek(LitStr) => { 49 | let s = input.parse::().unwrap(); 50 | ret.append(&mut Vec::from(s.value())); 51 | }, 52 | | _case if snoopy.peek(LitByteStr) => { 53 | let s = input.parse::().unwrap(); 54 | ret.append(&mut Vec::from(s.value())); 55 | }, 56 | $( 57 | | _case if snoopy.peek(kw::$macro) => { 58 | let _: kw::$macro = input.parse().unwrap(); 59 | let _: Token![!] = input.parse()?; 60 | let contents = input.parse::<::proc_macro2::Group>()?.stream(); 61 | let Self(ref mut bytes) = parse2(contents)?; 62 | ret.append(bytes); 63 | }, 64 | )* 65 | | _case if snoopy.peek(kw::stringify) => { 66 | let _: kw::stringify = input.parse().unwrap(); 67 | let _: Token![!] = input.parse()?; 68 | let contents = input.parse::<::proc_macro2::Group>()?.stream(); 69 | ret.append(&mut Vec::from(contents.to_string())); 70 | }, 71 | | _default => return Err(snoopy.error()), 72 | } 73 | let _: Option = input.parse()?; 74 | } 75 | return Ok(Self(ret)); 76 | )} 77 | supported_macros!(as_bytes, c, c_str, concat, concat_bytes); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/proc_macros/mod.rs: -------------------------------------------------------------------------------- 1 | //! Crate not intended for direct use. 2 | //! Use https:://docs.rs/byte-strings instead. 3 | // Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template 4 | #![allow(nonstandard_style, unused_imports)] 5 | 6 | use ::core::{ 7 | mem, 8 | ops::Not as _, 9 | }; 10 | use ::proc_macro::{ 11 | TokenStream, 12 | }; 13 | use ::proc_macro2::{ 14 | Span, 15 | TokenStream as TokenStream2, 16 | TokenTree as TT, 17 | }; 18 | use ::quote::{ 19 | format_ident, 20 | quote, 21 | quote_spanned, 22 | ToTokens, 23 | }; 24 | use ::syn::{*, 25 | parse::{Parse, Parser, ParseStream}, 26 | punctuated::Punctuated, 27 | Result, // Explicitly shadow it 28 | spanned::Spanned, 29 | }; 30 | 31 | use input_bytes::Input; 32 | mod input_bytes; 33 | 34 | #[proc_macro] pub 35 | fn concat_bytes ( 36 | input: TokenStream, 37 | ) -> TokenStream 38 | { 39 | concat_bytes_impl(input.into()) 40 | // .map(|ret| { println!("{}", ret); ret }) 41 | .unwrap_or_else(|err| { 42 | let mut errors = 43 | err .into_iter() 44 | .map(|err| Error::new( 45 | err.span(), 46 | format_args!("`#[byte_strings::concat_bytes]`: {}", err), 47 | )) 48 | ; 49 | let mut err = errors.next().unwrap(); 50 | errors.for_each(|cur| err.combine(cur)); 51 | err.to_compile_error() 52 | }) 53 | .into() 54 | } 55 | 56 | fn concat_bytes_impl ( 57 | input: TokenStream2, 58 | ) -> Result 59 | { 60 | let Input(_, ref mut bytes) = parse2(input)?; 61 | let byte_string_literal = LitByteStr::new(bytes, Span::call_site()); 62 | Ok(byte_string_literal.into_token_stream()) 63 | } 64 | 65 | #[proc_macro] pub 66 | fn c_str ( 67 | input: TokenStream, 68 | ) -> TokenStream 69 | { 70 | c_str_impl(input.into()) 71 | // .map(|ret| { println!("{}", ret); ret }) 72 | .unwrap_or_else(|err| { 73 | let mut errors = 74 | err .into_iter() 75 | .map(|err| Error::new( 76 | err.span(), 77 | format_args!("`#[byte_strings::c_str]`: {}", err), 78 | )) 79 | ; 80 | let mut err = errors.next().unwrap(); 81 | errors.for_each(|cur| err.combine(cur)); 82 | err.to_compile_error() 83 | }) 84 | .into() 85 | } 86 | 87 | fn c_str_impl ( 88 | input: TokenStream2, 89 | ) -> Result 90 | { 91 | let Input(ref crate_, ref mut bytes) = parse2(input)?; 92 | match bytes.iter().position(|&b| b == b'\0') { 93 | | Some(i) if i < bytes.len() - 1 => { 94 | // Not the last byte: error! 95 | return Err(Error::new( 96 | Span::call_site(), 97 | format!("Inner null byte at index {}", i), 98 | )); 99 | }, 100 | | None => { 101 | // No terminating null byte: add it! 102 | bytes.reserve_exact(1); 103 | bytes.push(b'\0'); 104 | }, 105 | | Some(_last_byte) => { 106 | // Terminating null byte already present: nothing to do. 107 | }, 108 | } 109 | let byte_string_literal = LitByteStr::new(bytes, Span::call_site()); 110 | Ok(quote!( 111 | { 112 | #[allow(unused_unsafe)] { 113 | unsafe { 114 | #crate_::__::core::ffi::CStr::from_bytes_with_nul_unchecked( 115 | #byte_string_literal 116 | ) 117 | } 118 | } 119 | } 120 | )) 121 | } 122 | -------------------------------------------------------------------------------- /tests/consts.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "const-friendly")] 2 | #[macro_use] 3 | extern crate byte_strings; 4 | 5 | const THIRD: &str = ""; 6 | 7 | #[test] 8 | fn concat_bytes() { 9 | const GREETING: &str = "Hello"; 10 | const MESSAGE: &[u8; 13] = const_concat_bytes!(GREETING, ", World!", THIRD); 11 | assert_eq!(MESSAGE, b"Hello, World!"); 12 | } 13 | 14 | 15 | #[test] 16 | fn concat() { 17 | const GREETING: &str = "Hello"; 18 | const MESSAGE: &str = const_concat!(GREETING, ", World!", THIRD); 19 | assert_eq!(MESSAGE, "Hello, World!"); 20 | } 21 | 22 | #[test] 23 | fn c_str() { 24 | use ::byte_strings::const_; 25 | 26 | const MESSAGE: &str = "Hello, World!"; 27 | const C_MESSAGE: &const_::CStr = const_::c_str!(MESSAGE, THIRD); 28 | assert_eq!( 29 | C_MESSAGE.to_bytes_with_nul(), 30 | b"Hello, World!\0", 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /tests/main.rs: -------------------------------------------------------------------------------- 1 | // Try to mess up with the crates' namespace. 2 | #[macro_use] 3 | extern crate byte_strings as core; 4 | extern crate core as byte_strings; 5 | 6 | #[test] 7 | fn basic () 8 | { 9 | let _: &'static [u8; 0] = concat_bytes!(); 10 | let _: &'static [u8; 0] = concat_bytes!(""); 11 | assert_eq!( 12 | as_bytes!("Hi"), b"Hi", 13 | ); 14 | 15 | use ::std::ffi::CStr as CStr_; 16 | let c: &'static CStr_ = c!(); 17 | assert_eq!(c.to_bytes_with_nul(), b"\0"); 18 | let c: &'static CStr_ = c!(""); 19 | assert_eq!(c.to_bytes_with_nul(), b"\0"); 20 | let c: &'static CStr_ = c!("\0"); 21 | assert_eq!(c.to_bytes_with_nul(), b"\0"); 22 | } 23 | 24 | #[test] 25 | fn nested () 26 | { 27 | assert_eq!( 28 | concat_bytes!("Hello, ", "World!"), 29 | b"Hello, World!", 30 | ); 31 | assert_eq!( 32 | as_bytes!(concat!("Hello, ", "World!")), 33 | b"Hello, World!", 34 | ); 35 | assert_eq!( 36 | as_bytes!(concat!("Hello, ", "World"), stringify!(!)), 37 | b"Hello, World!", 38 | ); 39 | } 40 | 41 | #[test] 42 | fn c_str () 43 | { 44 | let static_bytes = |c: &'static ::std::ffi::CStr| c.to_bytes_with_nul(); 45 | assert_eq!(static_bytes(c!("Hell")), b"Hell\0"); 46 | assert_eq!(static_bytes(c!("Hell\0")), b"Hell\0"); 47 | assert_eq!(static_bytes(c!("Hell", "\0")), b"Hell\0"); 48 | assert_eq!(static_bytes(c!("Hell", "o!")), b"Hello!\0"); 49 | } 50 | --------------------------------------------------------------------------------