diff --git a/.idea/advanced_rust.iml b/.idea/advanced_rust.iml index b833a0e..e9036cf 100644 --- a/.idea/advanced_rust.iml +++ b/.idea/advanced_rust.iml @@ -18,6 +18,11 @@ + + + + + diff --git a/Cargo.lock b/Cargo.lock index 3ea5ed3..f27061b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -11,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "app" version = "0.1.0" @@ -18,8 +39,132 @@ dependencies = [ "chapter01", "chapter02", "serde", - "tracing", - "tracing-subscriber", +] + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "askama" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", +] + +[[package]] +name = "askama_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +dependencies = [ + "askama_shared", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_shared" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" +dependencies = [ + "askama_escape", + "humansize", + "mime", + "mime_guess", + "nom", + "num-traits", + "percent-encoding", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "toml", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", ] [[package]] @@ -32,6 +177,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "chapter01" version = "0.1.0" dependencies = [ + "env_logger", + "log", "serde", "tracing", "tracing-subscriber", @@ -94,6 +241,45 @@ version = "0.1.0" name = "chapter12-unsafe" version = "0.1.0" +[[package]] +name = "chapter13-ordering" +version = "0.1.0" + +[[package]] +name = "chapter14-concurrecy" +version = "0.1.0" +dependencies = [ + "anyhow", + "blake3", + "futures", + "parking_lot", + "rayon", + "serde", + "serde_yaml", + "tokio", + "tokio-stream", + "tokio-util", + "toml", +] + +[[package]] +name = "chapter15-macro" +version = "0.1.0" +dependencies = [ + "anyhow", + "askama", + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -120,12 +306,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] -name = "deranged" -version = "0.3.11" +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "powerfmt", + "darling_core", + "quote", + "syn 1.0.109", ] [[package]] @@ -134,6 +346,25 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -143,6 +374,140 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -153,6 +518,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.11" @@ -166,26 +541,92 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "log" -version = "0.4.22" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "matchers" -version = "0.1.0" +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "regex-automata 0.1.10", + "autocfg", + "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -197,10 +638,22 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] [[package]] name = "once_cell" @@ -214,6 +667,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -227,10 +703,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" @@ -271,24 +747,24 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.6" +name = "redox_syscall" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "bitflags", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "regex" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ - "regex-syntax 0.6.29", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -299,20 +775,20 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "regex-syntax" -version = "0.8.4" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "ryu" @@ -320,6 +796,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.208" @@ -337,7 +819,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -352,6 +834,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -361,12 +855,63 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.75" @@ -378,6 +923,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -395,7 +949,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -409,50 +963,82 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.3.36" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", + "tinyvec_macros", ] [[package]] -name = "time-core" -version = "0.1.2" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "time-macros" -version = "0.2.18" +name = "tokio" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "num-conv", - "time-core", + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "tinyvec_macros", + "proc-macro2", + "quote", + "syn 2.0.75", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "tokio-stream" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] [[package]] name = "tracing" @@ -473,7 +1059,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -497,36 +1083,27 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "matchers", "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", - "time", - "tracing", "tracing-core", "tracing-log", - "tracing-serde", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", ] [[package]] @@ -567,6 +1144,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -583,8 +1172,99 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 960746c..c7f5031 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,20 @@ [workspace] -members = ["app", "chapter01-log", - "chapter02-feature", "chapter03-ownership", - "chapter04-lifecycle", "chapter05-memory", "chapter06-type", - "chapter07-pointer", "chapter08-vec", "chapter09-hash", "chapter10-error", "chapter11-closure", "chapter12-unsafe"] +members = ["app", + "chapter01-log", + "chapter02-feature", + "chapter03-ownership", + "chapter04-lifecycle", + "chapter05-memory", + "chapter06-type", + "chapter07-pointer", + "chapter08-vec", + "chapter09-hash", + "chapter10-error", + "chapter11-closure", + "chapter12-unsafe", + "chapter13-atomic_n_ordering", + "chapter14-concurrecy", + "chapter15-macro"] [workspace.dependencies] diff --git a/README.md b/README.md index 1790d59..2698300 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [**rust 背包客**](#rust-%E8%83%8C%E5%8C%85%E5%AE%A2) + - [第一章:log 日志](#%E7%AC%AC%E4%B8%80%E7%AB%A0log-%E6%97%A5%E5%BF%97) - [第二章:feature 条件编译](#%E7%AC%AC%E4%BA%8C%E7%AB%A0feature-%E6%9D%A1%E4%BB%B6%E7%BC%96%E8%AF%91) - [第三章:所有权](#%E7%AC%AC%E4%B8%89%E7%AB%A0%E6%89%80%E6%9C%89%E6%9D%83) - [第四章:生命周期](#%E7%AC%AC%E5%9B%9B%E7%AB%A0%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) @@ -14,6 +15,9 @@ - [第十章:error 错误处理](#%E7%AC%AC%E5%8D%81%E7%AB%A0error-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86) - [第十一章:闭包](#%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AB%A0%E9%97%AD%E5%8C%85) - [第十二章:unsafe](#%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AB%A0unsafe) + - [第十三章:atomic and ordering](#%E7%AC%AC%E5%8D%81%E4%B8%89%E7%AB%A0atomic-and-ordering) + - [第十四章:并发处理](#%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AB%A0%E5%B9%B6%E5%8F%91%E5%A4%84%E7%90%86) + - [第十五章:宏编程](#%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%E5%AE%8F%E7%BC%96%E7%A8%8B) - [参考](#%E5%8F%82%E8%80%83) @@ -24,6 +28,11 @@ [cargo workspace来管理多个package](workspace.md) +## [第一章:log 日志](chapter01-log/log.md) + +- 1 env_logger 记录日志 +- 2 tracing 记录日志 + ## [第二章:feature 条件编译](chapter02-feature/feature.md) ## [第三章:所有权](chapter03-ownership/ownership.md) @@ -73,7 +82,7 @@ - [2 动态数组 Vector 和 数组 [T; n] 如何转化成 &[T]](chapter08-vec/src/vec2-slice.rs) - [3 String、&String 转换成 &str](chapter08-vec/src/vec3-string-slice.rs) -## [第九章:hash](chapter09-hash/hashmap.md) +## [第九章:hash](chapter09-hash/hash.md) - [1 hashmap cap 扩容和缩容](chapter09-hash/src/hash1-cap.rs) - 2 HashSet @@ -91,10 +100,39 @@ - [1 开发者还是调用者保证内存安全](chapter12-unsafe/src/unsafe1-caller.rs) +## [第十三章:atomic and ordering](chapter13-atomic_n_ordering/ordering.md) + +- 1 验证 Ordering 的可见性:两线程并发增加值 + - 容易大于 1000000 + - 最多只能 1000000 + +## 第十四章:并发处理 + +- [1 Condvar 条件变量](chapter14-concurrecy/src/01_convar.md) +- [2 Future](chapter14-concurrecy/src/02_future.md) + - 2.1 await 来执行 future,或则使用一个 executor 来执行 future + - 2.2 将值固定到栈上 + - 2.3 将值固定到堆上 + - 2.4 将固定住的 Future 变为 Unpin +- [3 tokio](chapter14-concurrecy/src/03_tokio.md) + - 3.1 #[tokio::main] 的作用 + - 3.2 消息通道: mpsc 多生产者,单消费者模式 + - 3.3 消息通道: watch 单生产者、多消费者 + +## [第十五章:宏编程](chapter15-macro/macro.md) + +- 1 声明宏(declarative macro) + - [1.1 vec! 实现](chapter15-macro/examples/macro1-declarativemacro.rs) + - [1.2 声明宏的 hygiene:宏内部代码和宏上下文相互影响](chapter15-macro/examples/macro2-declarativemacro-hygiene.rs) +- 2 过程宏(procedural macro) + - 2.1 派生宏(derive macro) + - 2.2 属性宏(attribute macro) + ## 参考 - [Rust语言圣经](https://github.com/sunface/rust-course) - [Rust 程序设计语言](https://rustwiki.org/zh-CN/book/title-page.html) - [Rust Practice](https://github.com/sunface/rust-by-practice) - [rust-by-example](https://github.com/rust-lang/rust-by-example) +- [Rust 秘典(死灵书)](https://github.com/rust-lang-cn/nomicon-zh-Hans) - [陈天 · Rust 编程第一课](https://time.geekbang.org/column/intro/100085301?tab=catalog) \ No newline at end of file diff --git a/app/Cargo.toml b/app/Cargo.toml index c7e5b00..a7c1f9e 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -5,11 +5,5 @@ edition = "2021" [dependencies] serde = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = [ - "env-filter", - "json", - "time", -], optional = true } -chapter01= {path = "../chapter01-log" } -chapter02= {path = "../chapter02-feature",features = ["print-b"],default-features = false} \ No newline at end of file +chapter01 = { path = "../chapter01-log" } +chapter02 = { path = "../chapter02-feature", features = ["print-b"], default-features = false } \ No newline at end of file diff --git a/app/src/main.rs b/app/src/main.rs index 87246a5..d4cdfcd 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -1,13 +1,5 @@ -use chapter01::{add, debug, info, init_config}; use chapter02::print_feature; fn main() { - - let result = add(1,2); - println!("Hello, world! {}",result); print_feature(); - init_config(); - info!("tracing log"); - - } diff --git a/chapter01-log/Cargo.toml b/chapter01-log/Cargo.toml index 563d8a2..a15c57a 100644 --- a/chapter01-log/Cargo.toml +++ b/chapter01-log/Cargo.toml @@ -1,12 +1,15 @@ [package] name = "chapter01" -version = { workspace = true} -edition = { workspace = true} -authors = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +authors = { workspace = true } [dependencies] serde = { workspace = true } tracing = { workspace = true } -tracing-subscriber = { workspace = true } \ No newline at end of file +tracing-subscriber = { workspace = true } + +log = "0.4" +env_logger = "0.9" \ No newline at end of file diff --git a/chapter01-log/examples/log1-env_logger.rs b/chapter01-log/examples/log1-env_logger.rs new file mode 100644 index 0000000..d18409e --- /dev/null +++ b/chapter01-log/examples/log1-env_logger.rs @@ -0,0 +1,16 @@ +use log::{debug, error, info, log_enabled, Level}; + +fn main() { + // 注意,env_logger 必须尽可能早的初始化 + env_logger::init(); + + debug!("this is a debug {}", "message"); + error!("this is printed by default"); + + if log_enabled!(Level::Info) { + let x = 3 * 4; // expensive computation + info!("the answer was: {}", x); + } +} + +// RUST_LOG=info cargo run --color=always --example log1-std --manifest-path ./chapter01-log/Cargo.toml diff --git a/chapter01-log/examples/log2-tracing.rs b/chapter01-log/examples/log2-tracing.rs new file mode 100644 index 0000000..2a01461 --- /dev/null +++ b/chapter01-log/examples/log2-tracing.rs @@ -0,0 +1,7 @@ +use chapter01::init_config; +use log::info; + +fn main() { + init_config(); + info!("tracing log"); +} diff --git a/chapter01-log/log.md b/chapter01-log/log.md index b4b4176..a7ed85c 100644 --- a/chapter01-log/log.md +++ b/chapter01-log/log.md @@ -4,18 +4,115 @@ - [tracing](#tracing) - [tracing 各个模块](#tracing-%E5%90%84%E4%B8%AA%E6%A8%A1%E5%9D%97) + - [log 门面](#log-%E9%97%A8%E9%9D%A2) - -# tracing +# tracing tracing 支持 log 门面库的 API,因此,它既可以作为分布式追踪的 SDK 来使用,也可以作为日志库来使用。 ## tracing 各个模块 -- tracing : 作用域内的结构化日志记录和诊断系统。 -- tracing_appender : 记录事件和跨度的编写者。也就是将日志写入文件或者控制台。 -- tracing_error : 增强错误处理跟踪诊断信息的实用工具。 -- tracing_flame : 用于生成折叠堆栈跟踪以生成Inferno火焰图和火焰图表的跟踪订阅者。 -- tracing_log : 用于连接标准库日志系统和tracing系统的连接器。 -- tracing_subscriber : 用于实现和组成tracing订阅者的工具集合。 \ No newline at end of file + +- tracing: 作用域内的结构化日志记录和诊断系统。 +- tracing_appender: 记录事件和跨度的编写者。也就是将日志写入文件或者控制台。 +- tracing_error: 增强错误处理跟踪诊断信息的实用工具。 +- tracing_flame: 用于生成折叠堆栈跟踪以生成Inferno火焰图和火焰图表的跟踪订阅者。 +- tracing_log: 用于连接标准库日志系统和tracing系统的连接器。 +- tracing_subscriber: 用于实现和组成tracing订阅者的工具集合。 + +## log 门面 + +github.com/rust-lang/log + +```rust +// log-0.4.22/src/lib.rs +pub trait Log: Sync + Send { + // 判断某条带有元数据的日志是否能被记录 + fn enabled(&self, metadata: &Metadata) -> bool; + + // 记录 record 所代表的日志 + fn log(&self, record: &Record); + + // 将缓存中的日志数据刷到输出中,例如标准输出或者文件中 + fn flush(&self); +} +``` + +log 仅仅是日志门面库,它并不具备完整的日志库功能!因此你无法在控制台中看到任何日志输出 + +env_logger 实现 + +```rust +// env_logger-0.9.3/src/lib.rs +impl Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.filter.enabled(metadata) + } + + fn log(&self, record: &Record) { + if self.matches(record) { + // Log records are written to a thread-local buffer before being printed + // to the terminal. We clear these buffers afterwards, but they aren't shrinked + // so will always at least have capacity for the largest log record formatted + // on that thread. + // + // If multiple `Logger`s are used by the same threads then the thread-local + // formatter might have different color support. If this is the case the + // formatter and its buffer are discarded and recreated. + + thread_local! { + static FORMATTER: RefCell> = RefCell::new(None); + } + + let print = |formatter: &mut Formatter, record: &Record| { + let _ = + (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); + + // Always clear the buffer afterwards + formatter.clear(); + }; + + let printed = FORMATTER + .try_with(|tl_buf| { + match tl_buf.try_borrow_mut() { + // There are no active borrows of the buffer + Ok(mut tl_buf) => match *tl_buf { + // We have a previously set formatter + Some(ref mut formatter) => { + // Check the buffer style. If it's different from the logger's + // style then drop the buffer and recreate it. + if formatter.write_style() != self.writer.write_style() { + *formatter = Formatter::new(&self.writer); + } + + print(formatter, record); + } + // We don't have a previously set formatter + None => { + let mut formatter = Formatter::new(&self.writer); + print(&mut formatter, record); + + *tl_buf = Some(formatter); + } + }, + // There's already an active borrow of the buffer (due to re-entrancy) + Err(_) => { + print(&mut Formatter::new(&self.writer), record); + } + } + }) + .is_ok(); + + if !printed { + // The thread-local storage was not available (because its + // destructor has already run). Create a new single-use + // Formatter on the stack for this call. + print(&mut Formatter::new(&self.writer), record); + } + } + } + + fn flush(&self) {} +} +``` diff --git a/chapter06-type/src/type3-related-trait.rs b/chapter06-type/src/type3-related-trait.rs index 473616e..968c33e 100644 --- a/chapter06-type/src/type3-related-trait.rs +++ b/chapter06-type/src/type3-related-trait.rs @@ -1,6 +1,6 @@ +use regex::Regex; use std::str::FromStr; -use regex::Regex; pub trait Parse { type Error; fn parse(s: &str) -> Result diff --git a/chapter09-hash/hashmap.md b/chapter09-hash/hash.md similarity index 100% rename from chapter09-hash/hashmap.md rename to chapter09-hash/hash.md diff --git a/chapter11-closure/closure.md b/chapter11-closure/closure.md index 9dc7b29..e447c10 100644 --- a/chapter11-closure/closure.md +++ b/chapter11-closure/closure.md @@ -5,7 +5,7 @@ - [闭包](#%E9%97%AD%E5%8C%85) - [案例](#%E6%A1%88%E4%BE%8B) - [Rust 的闭包类型](#rust-%E7%9A%84%E9%97%AD%E5%8C%85%E7%B1%BB%E5%9E%8B) - - [FnOnce 只能被调用一次](#fnonce-%E5%8F%AA%E8%83%BD%E8%A2%AB%E8%B0%83%E7%94%A8%E4%B8%80%E6%AC%A1) + - [FnOnce](#fnonce) - [FnMut](#fnmut) - [Fn](#fn) @@ -40,7 +40,9 @@ where ![img.png](closure-type.png) -### FnOnce 只能被调用一次 +### FnOnce + +FnOnce 只能被调用一次 ```rust #[fundamental] // so that regex can rely that `&str: !FnMut` diff --git a/chapter12-unsafe/unsafe.md b/chapter12-unsafe/unsafe.md index 1078eca..c85aabb 100644 --- a/chapter12-unsafe/unsafe.md +++ b/chapter12-unsafe/unsafe.md @@ -4,6 +4,7 @@ - [unsafe](#unsafe) - [使用 unsafe Rust 的原因](#%E4%BD%BF%E7%94%A8-unsafe-rust-%E7%9A%84%E5%8E%9F%E5%9B%A0) + - [标准库中 unsafe 的函数](#%E6%A0%87%E5%87%86%E5%BA%93%E4%B8%AD-unsafe-%E7%9A%84%E5%87%BD%E6%95%B0) @@ -15,4 +16,13 @@ 指令集)。这样的操作,编译器是无法保证内存安全 - 当 Rust 要访问其它语言比如 C/C++ 的库,因为它们并不满足 Rust 的安全性要求,这种跨语言的 FFI(Foreign Function Interface),也是 unsafe 的 -- 使用 unsafe Rust 纯粹是为了性能。比如略过边界检查、使用未初始化内存等。这样的 unsafe 我们要尽量不用 \ No newline at end of file +- 使用 unsafe Rust 纯粹是为了性能。比如略过边界检查、使用未初始化内存等。这样的 unsafe 我们要尽量不用 + +## 标准库中 unsafe 的函数 + +距离 + +- slice::get_unchecked,它不会检查传入索引的有效性,允许违反内存安全的规则。 +- mem::transmute将一些数据重新解释为给定的类型,绕过类型安全的规则(详见conversions)。 +- 每一个指向一个 Sized 类型的原始指针都有一个offset方法,如果传递的偏移量不在“界内”,则该调用是未定义行为。 +- 所有 FFI(外部函数接口 Foreign Function Interface)函数的调用都是unsafe的,因为 Rust 编译器无法检查其他语言的操作。 \ No newline at end of file diff --git a/chapter13-atomic_n_ordering/Cargo.toml b/chapter13-atomic_n_ordering/Cargo.toml new file mode 100644 index 0000000..24fad51 --- /dev/null +++ b/chapter13-atomic_n_ordering/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "chapter13-ordering" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[[bin]] +name = "ordering1" +path = "src/ordering1.rs" + + +[[bin]] +name = "ordering2" +path = "src/ordering2.rs" diff --git a/chapter13-atomic_n_ordering/ordering.md b/chapter13-atomic_n_ordering/ordering.md new file mode 100644 index 0000000..ee1afe1 --- /dev/null +++ b/chapter13-atomic_n_ordering/ordering.md @@ -0,0 +1,76 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [数据访问](#%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE) +- [指令重排](#%E6%8C%87%E4%BB%A4%E9%87%8D%E6%8E%92) +- [ordering](#ordering) + - [Ordering 的可见性](#ordering-%E7%9A%84%E5%8F%AF%E8%A7%81%E6%80%A7) + + + +## 数据访问 + +假设要实现一个计数功能,每次对变量执行+1的操作。CPU执行的时候就需要顺序执行三个操作 + +1. 从内存中读取变量的值 +2. 加1 +3. 写回到内存中 + +如果有两个线程同时操作了这个值,这三个操作可能是交叉的,导致结果不正确 + +## 指令重排 + +```rust +fn f(a: &mut i32, b: &mut i32) { + *a += 1; + *b += 1; + *a += 1; +} +``` + +交给操作系统编译执行,但很可能你得到的是这样的 + +```rust +fn f(a: &mut i32, b: &mut i32) { + *a += 2; + *b += 1; +} +``` + +**只要不影响程序语义,指令可以重排执行以优化,即不按代码顺序执行。** + +单线程下这样问题可能还不大,但如果多线程下,同一线程下多条原子指令,也是会有指令重排的可能,数据竞争很有可能发生,就是说加了原子操作也无法确定数据操作顺序 + +。 + +## ordering + +Rust用于的内存访问顺序(memory order)的Ordering基本和`C++ 20`的内存排序的保持一致 + +```rust +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[non_exhaustive] +#[rustc_diagnostic_item = "Ordering"] +pub enum Ordering { + Relaxed, + Release, + Acquire, + AcqRel, + SeqCst, +} +``` + +- Relaxed,这是最宽松的规则,它对编译器和 CPU 不做任何限制,可以乱序执行。 +- Release,适用于写数据操作 + - 当前线程不能有其他的读或写被 reorder 在 store 之后当前写入后的结果对其他线程的同一数据 Acquire 读取操作是可见的 +- Acquire,适用于读取数据操作 + - 当前线程不能有其他的读或写被 reorder 在 load 之前其他线程的同一数据已发生的 Release 写入操作都是对其可见的 +- AcqRel ,是 Acquire 和 Release 的结合 + - 一般用在 fetch_xxx 上,比如你要对一个 atomic 自增 1,你希望这个操作之前和之后的读取或写入操作不会被乱序,并且操作的结果对其它线程可见。 +- SeqCst,除了 AcqRel 的保证外,它还保证所有线程看到的所有 SeqCst 操作的顺序是一致的。 + +### Ordering 的可见性 + +[案例一](./src/ordering1.rs) \ No newline at end of file diff --git a/chapter13-atomic_n_ordering/src/ordering1.rs b/chapter13-atomic_n_ordering/src/ordering1.rs new file mode 100644 index 0000000..a5a2294 --- /dev/null +++ b/chapter13-atomic_n_ordering/src/ordering1.rs @@ -0,0 +1,42 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, + thread, +}; + +static S: AtomicU64 = AtomicU64::new(0); +fn relaxed() { + let a = Arc::new(AtomicBool::new(false)); + let b = Arc::new(AtomicBool::new(false)); + let a_clone = a.clone(); + let b_clone = b.clone(); + + let t1 = thread::spawn(move || { + a.store(true, Ordering::Relaxed); + if !b.load(Ordering::Relaxed) { + S.fetch_add(1, Ordering::Relaxed); + } + }); + + let t2 = thread::spawn(move || { + b_clone.store(true, Ordering::Relaxed); + if !a_clone.load(Ordering::Relaxed) { + S.fetch_add(1, Ordering::Relaxed); + } + }); + + t1.join().unwrap(); + t2.join().unwrap(); +} + +fn main() { + let cnt = 100000; + for _ in 0..cnt { + relaxed(); + } + // 结果可能大于10000 + let s = S.load(Ordering::SeqCst); + println!("s: {}", s); +} diff --git a/chapter13-atomic_n_ordering/src/ordering2.rs b/chapter13-atomic_n_ordering/src/ordering2.rs new file mode 100644 index 0000000..951ab0c --- /dev/null +++ b/chapter13-atomic_n_ordering/src/ordering2.rs @@ -0,0 +1,42 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, + thread, +}; + +static S: AtomicU64 = AtomicU64::new(0); +fn relaxed() { + let a = Arc::new(AtomicBool::new(false)); + let b = Arc::new(AtomicBool::new(false)); + let a_clone = a.clone(); + let b_clone = b.clone(); + + let t1 = thread::spawn(move || { + a.store(true, Ordering::SeqCst); + if !b.load(Ordering::SeqCst) { + S.fetch_add(1, Ordering::Relaxed); + } + }); + + let t2 = thread::spawn(move || { + b_clone.store(true, Ordering::SeqCst); + if !a_clone.load(Ordering::SeqCst) { + S.fetch_add(1, Ordering::Relaxed); + } + }); + + t1.join().unwrap(); + t2.join().unwrap(); +} + +fn main() { + let cnt = 100000; + for _ in 0..cnt { + relaxed(); + } + // 结果可能大于10000 + let s = S.load(Ordering::SeqCst); + println!("s: {}", s); +} diff --git a/chapter14-concurrecy/Cargo.toml b/chapter14-concurrecy/Cargo.toml new file mode 100644 index 0000000..11cc962 --- /dev/null +++ b/chapter14-concurrecy/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "chapter14-concurrecy" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[[bin]] +name = "concurrency1" +path = "src/concurrency1_convar.rs" + +[[bin]] +name = "concurrency2" +path = "src/concurrency2_future.rs" + +[[bin]] +name = "concurrency3" +path = "src/concurrency3_stream.rs" + + +[[bin]] +name = "concurrency4" +path = "src/concurrency4_pin_stack.rs" + +[[bin]] +name = "concurrency5" +path = "src/concurrency5_pin_heap.rs" + +[[bin]] +name = "concurrency6" +path = "src/concurrency6_tokio_main.rs" + +[[bin]] +name = "concurrency7" +path = "src/concurrency7_tokio_mpsc.rs" + + +[[bin]] +name = "concurrency8" +path = "src/concurrency8_tokio_watch.rs" + +[[bin]] +name = "concurrency9" +path = "src/concurrency9_tokio_select.rs" + +[dependencies] +parking_lot = "0.12" +anyhow = "1" +blake3 = "1" +futures = "0.3" +rayon = "1" +serde = { version = "1", features = ["derive"] } +serde_yaml = "0.8" +tokio = { version = "1", features = ["full"] } +tokio-util = { version = "0.6", features = ["codec"] } +toml = "0.5" +tokio-stream = "0.1.15" + + diff --git a/chapter14-concurrecy/concurrency.md b/chapter14-concurrecy/concurrency.md new file mode 100644 index 0000000..9e5a904 --- /dev/null +++ b/chapter14-concurrecy/concurrency.md @@ -0,0 +1,29 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [异步运行时](#%E5%BC%82%E6%AD%A5%E8%BF%90%E8%A1%8C%E6%97%B6) + - [executor](#executor) + + + +## 异步运行时 + +- reactor 会利用操作系统提供的异步 I/O,比如 epoll / kqueue / IOCP,来监听操作系统提供的 IO 事件,当遇到满足条件的事件时,就会调用 + Waker.wake() 唤醒被挂起的 Future。这个 Future 会回到 ready queue 等待执行 +- executor 用于调度和执行相应的任务( Future ): tokio 的调度器(executor)会运行在多个线程上,运行线程自己的 ready queue + 上的任务(Future),如果没有,就去别的线程的调度器上“偷”一些过来运行。当某个任务无法再继续取得进展,此时 Future 运行的结果是 + Poll::Pending,那么调度器会挂起任务,并设置好合适的唤醒条件(Waker),等待被 reactor 唤醒。 + +异步编程依赖于两个关键概念:Future 和 async/await + +### executor + +executor 大致想象成一个 Future 的调度器。 + +常见的 executor 有: + +- futures 库自带的很简单的 executor +- tokio 提供的 executor,当使用 #[tokio::main] 时,就隐含引入了 tokio 的 executor; +- async-std 提供的 executor,和 tokio 类似; +- smol 提供的 async-executor,主要提供了 block_on \ No newline at end of file diff --git a/chapter14-concurrecy/src/01_convar.md b/chapter14-concurrecy/src/01_convar.md new file mode 100644 index 0000000..7c2de9b --- /dev/null +++ b/chapter14-concurrecy/src/01_convar.md @@ -0,0 +1,50 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Condvar 条件变量](#condvar-%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F) + - [使用](#%E4%BD%BF%E7%94%A8) + - [parking_lot的一些关键特点:](#parking_lot%E7%9A%84%E4%B8%80%E4%BA%9B%E5%85%B3%E9%94%AE%E7%89%B9%E7%82%B9) + + + +# Condvar 条件变量 + +condition var 条件变量代表阻止线程的能力,使其在等待事件发生时不消耗CPU时间。 +在其他语言中也有类似的概念,叫做栅栏,闭锁,屏障,信号量等。他们具有相同的意义。 + +在介绍条件变量之前,先介绍屏障(Barrier)。屏障相当于一堵带门的墙,使用wait方法,在某个点阻塞全部进入临界区的线程。条件变量(Condition +Variable)和屏障的语义类似,但它不是阻塞全部线程,而是在满足某些特定条件之前阻塞某一个得到互斥锁的线程 + +## 使用 + +- CondVar需要和锁一起使用,在运行中每个条件变量每次只可以和一个互斥体一起使用。 + +这个接口需要一个 MutexGuard,以便于知道需要唤醒哪个 Mutex 下等待的线程. + +推荐使用 [parking_lot](https://crates.io/crates/parking_lot)的实现。 +parking_lot的设计精髓在于其自适应的锁机制。在无竞争状态下,锁的获取和释放仅需一次原子操作; +微竞争时,会进行几次循环尝试,而不是立即挂起线程;当竞争激烈时,线程会在尝试一段时间后被暂停,避免过度消耗CPU资源。 +此外,该库还支持硬件锁消除,进一步提高了读取密集型任务的性能 + +```rust +// parking_lot-0.12.3/src/condvar.rs +impl Condvar { + #[inline] + pub fn wait(&self, mutex_guard: &mut MutexGuard<'_, T>) { + self.wait_until_internal(unsafe { MutexGuard::mutex(mutex_guard).raw() }, None); + } +} +``` + +### parking_lot的一些关键特点: + +- 超小的存储开销:Mutex和Once只需要1字节,而Condvar和RwLock只占1个机器字。 +- 快速无竞争和快速释放:单次原子操作完成。 +- 微竞争优化:自适应循环尝试。 +- 硬件锁消除:对于具备硬件支持的处理器,提升读性能。 +- 可选的死锁检测:实验性特性,需开启deadlock_detection。 +- 支持序列化(serde):通过启用serde特性。 +- 可发送的锁守卫:通过启用send_guard特性。 +- 平均公平性:避免长时间等待。 +- 递归锁:通过ReentrantMutex类型实现 diff --git a/chapter14-concurrecy/src/02_future.md b/chapter14-concurrecy/src/02_future.md new file mode 100644 index 0000000..091eefb --- /dev/null +++ b/chapter14-concurrecy/src/02_future.md @@ -0,0 +1,217 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [future](#future) + - [前置 trait : pin/ unpin](#%E5%89%8D%E7%BD%AE-trait--pin-unpin) + - [future 实现](#future-%E5%AE%9E%E7%8E%B0) + - [waker 的 vtable 在第三方实现](#waker-%E7%9A%84-vtable-%E5%9C%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E5%AE%9E%E7%8E%B0) + - [async 实现](#async-%E5%AE%9E%E7%8E%B0) + - [stream](#stream) + - [futures-core](#futures-core) + + + +# future + +在异步操作里,异步处理完成后的结果,一般用 Promise 来保存,它是一个对象,用来描述在未来的某个时刻才能获得的结果的值 + +在很多支持异步的语言中,Promise 也叫 Future / Delay / Deferred 等。 + +JavaScript Promise 一般存在三个状态; + +1. 初始状态,Promise 还未运行; +2. 等待(pending)状态,Promise 已运行,但还未结束; +3. 结束状态,Promise 成功解析出一个值,或者执行失败。 + +async 定义了一个可以并发执行的任务,而 await 则触发这个任务并发执行。大多数语言中,async/await 是一个语法糖(syntactic +sugar),它使用状态机将 Promise 包装起来,让异步调用的使用感觉和同步调用非常类似,也让代码更容易阅读。 + +async 来方便地生成 Future,await 来触发 Future 的调度和执行。 + +## 前置 trait : pin/ unpin + +unpin: 在内存中安全地移动 + +```rust +// 自引用类型 +struct SelfRef { + value: String, + pointer_to_value: *mut String, +} + +``` + +pointer_to_value 是一个裸指针,指向第一个字段 value 持有的字符串 String 。很简单对吧?现在考虑一个情况, 若String 被移动了怎么办? + +此时一个致命的问题就出现了:新的字符串的内存地址变了,而 pointer_to_value 依然指向之前的地址,一个重大 bug 就出现了! + +实际上,Pin 不按套路出牌,它是一个结构体: + +```rust +#[derive(Copy, Clone)] +pub struct Pin { + #[unstable(feature = "unsafe_pin_internals", issue = "none")] + #[doc(hidden)] + pub __pointer: Ptr, +} +``` + +绝大多数类型都不在意是否被移动(开篇提到的第一种类型),因此它们都自动实现了 Unpin 特征。 + +## future 实现 + +```rust +#[doc(notable_trait)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +#[stable(feature = "futures_api", since = "1.36.0")] +#[lang = "future_trait"] +#[diagnostic::on_unimplemented( + label = "`{Self}` is not a future", + message = "`{Self}` is not a future" +)] +pub trait Future { + /// The type of value produced on completion. + #[stable(feature = "futures_api", since = "1.36.0")] + #[rustc_diagnostic_item = "FutureOutput"] + type Output; + + #[lang = "poll"] + #[stable(feature = "futures_api", since = "1.36.0")] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; +} +``` + +poll() 方法,这个方法返回 PollSelf::Output + +```rust +pub enum Poll { + /// Represents that a value is immediately ready. + #[lang = "Ready"] + #[stable(feature = "futures_api", since = "1.36.0")] + Ready(#[stable(feature = "futures_api", since = "1.36.0")] T), + + /// Represents that a value is not ready yet. + /// + /// When a function returns `Pending`, the function *must* also + /// ensure that the current task is scheduled to be awoken when + /// progress can be made. + #[lang = "Pending"] + #[stable(feature = "futures_api", since = "1.36.0")] + Pending, +} +``` + +Poll 是个 enum,包含 Ready 和 Pending 两个状态. + +- 当 Future 返回 Pending 状态时,活还没干完,但干不下去了,需要阻塞一阵子,等某个事件将其唤醒; +- 当 Future 返回 Ready 状态时,Future 对应的值已经得到, + +```rust +#[stable(feature = "futures_api", since = "1.36.0")] +#[lang = "Context"] +pub struct Context<'a> { + waker: &'a Waker, + local_waker: &'a LocalWaker, + // Ensure we future-proof against variance changes by forcing + // the lifetime to be invariant (argument-position lifetimes + // are contravariant while return-position lifetimes are + // covariant). + _marker: PhantomData &'a ()>, + // Ensure `Context` is `!Send` and `!Sync` in order to allow + // for future `!Send` and / or `!Sync` fields. + _marker2: PhantomData<*mut ()>, +} + +#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/66401 +#[stable(feature = "futures_api", since = "1.36.0")] +pub struct Waker { + waker: RawWaker, +} + +#[derive(PartialEq, Debug)] +#[stable(feature = "futures_api", since = "1.36.0")] +pub struct RawWaker { + /// A data pointer, which can be used to store arbitrary data as required + /// by the executor. This could be e.g. a type-erased pointer to an `Arc` + /// that is associated with the task. + /// The value of this field gets passed to all functions that are part of + /// the vtable as the first parameter. + data: *const (), + /// Virtual function pointer table that customizes the behavior of this waker. + vtable: &'static RawWakerVTable, +} +``` + +Context 就是 Waker 的一个封装。 + +### waker 的 vtable 在第三方实现 + +```rust +// https://github.com/rust-lang/futures-rs/blob/0.3.30/futures-task/src/waker.rs + +pub(super) fn waker_vtable() -> &'static RawWakerVTable { + &RawWakerVTable::new( + clone_arc_raw::, + wake_arc_raw::, + wake_by_ref_arc_raw::, + drop_arc_raw::, + ) +} + +``` + +## async 实现 + +Rust 在编译 async fn 或者 async block 时,就会生成类似的状态机的实现 + +## stream + +对于 Iterator,可以不断调用其 next() 方法,获得新的值,直到 Iterator 返回 None。Iterator 是阻塞式返回数据的,每次调用 next() +,必然独占 CPU 直到得到一个结果. + +异步的 Stream 是非阻塞的,在等待的过程中会空出 CPU 做其他事情。 + +### futures-core + +```rust +// futures-core-0.3.30/src/stream.rs + +#[must_use = "streams do nothing unless polled"] +pub trait Stream { + /// Values yielded by the stream. + type Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (0, None) + } +} +``` + +poll_next() 调用起来不方便,我们需要自己处理 Poll 状态, +所以StreamExt 提供了 next() 方法,返回一个实现了 Future trait 的 Next 结构 + +```rust +pub trait StreamExt: Stream { + fn next(&mut self) -> Next<'_, Self> + where + Self: Unpin, + { + assert_future::, _>(Next::new(self)) + } + + fn into_future(self) -> StreamFuture + where + Self: Sized + Unpin, + { + assert_future::<(Option, Self), _>(StreamFuture::new(self)) + } + + + // ... + +} +``` diff --git a/chapter14-concurrecy/src/03_tokio.md b/chapter14-concurrecy/src/03_tokio.md new file mode 100644 index 0000000..b0fc173 --- /dev/null +++ b/chapter14-concurrecy/src/03_tokio.md @@ -0,0 +1,123 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [tokio](#tokio) + - [Tokio 的任务调度](#tokio-%E7%9A%84%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6) + - [Tokio Reactor 模型](#tokio-reactor-%E6%A8%A1%E5%9E%8B) + - [Tokio 的消息通道( channel )](#tokio-%E7%9A%84%E6%B6%88%E6%81%AF%E9%80%9A%E9%81%93-channel-) + - [watch channel](#watch-channel) + - [tokio::select!](#tokioselect) + - [常见使用错误](#%E5%B8%B8%E8%A7%81%E4%BD%BF%E7%94%A8%E9%94%99%E8%AF%AF) + - [std::sync::Mutex 和 tokio::sync::Mutex](#stdsyncmutex-%E5%92%8C-tokiosyncmutex) + - [源码](#%E6%BA%90%E7%A0%81) + - [task 抽象](#task-%E6%8A%BD%E8%B1%A1) + - [参考](#%E5%8F%82%E8%80%83) + + + +# tokio + +Tokio 的核心是一套 M:N 的协程 Runtime,下层通过 Rust 协程和 Mio 驱动,支撑上层的 HTTP / RPC 应用。 + +主要由以下组件构成: + +- 多线程版本的异步运行时,可以运行使用 async/await 编写的代码 +- 标准库中阻塞 API 的异步版本,例如thread::sleep会阻塞当前线程,tokio中就提供了相应的异步实现版本 +- 构建异步编程所需的生态,甚至还提供了 tracing 用于日志和分布式追踪, 提供 console 用于 Debug 异步编程 + +![img.png](tokio_and_run_queue.png) + +## Tokio 的任务调度 + +![img.png](runtime-scheduler.png) + +Local Run Queue、 LIFO Slot、 Global Queue 都用于存储待处理的任务。Tokio Runtime 可以包含多个 Processor, +每个 Processor 都有自己的 Local Run Queue(Local Run Queue 的大小是固定的)和 LIFO Slot(目前 LIFO Slot 只能存放一个任务),所有的 +Processor 共享 Global Queue。 + +Processor 获取 task 后,会开始执行这个 task,在 task 执行过程中,可能会产生很多新的 task,第一个新 task 会被放到 LIFO Slot +中,其他新 task 会被放到 Local Run Queue 中,因为 Local Run Queue 的大小是固定的,如果它满了,剩余的 task 会被放到 Global +Queue 中。 + +Processor 运行完当前 task 后,会尝试按照以下顺序获取新的 task 并继续运行: + +1. LIFO Slot. +2. Local Run Queue. +3. Global Queue. +4. 其他 Processor 的 Local Run Queue。 + +## Tokio Reactor 模型 + +![img.png](reactor.png) + +一个异步任务的处理过程为: + +1. Processor 从 Task Queue 中获取一个 Task。 +2. Processor 判断这个 Task 为异步任务(Task.future.poll()返回 Pending),则将 Task 封装为 Waker,交给多路复用器。 +3. 多路复用器监听系统事件。 +4. 当事件出现时,多路复用器将事件和该事件对应的 Waker 交给事件队列。 +5. 事件循环处理事件,调用 Waker.wake(), 将 Task 重新放入 TaskQueue 中,待 Processor 处理 + +## Tokio 的消息通道( channel ) + +* mpsc, 多生产者,单消费者模式 +* oneshot, 单生产者,单消费者,一次只能发送一条消息。一旦发送,channel就会关闭 +* broadcast,多生产者,多消费者,其中每一条发送的消息都可以被所有接收者收到,因此是广播 +* watch,单生产者,多消费者,只保存一条最新的消息,因此接收者只能看到最近的一条消息,例如,这种模式适用于配置文件变化的监听 + +少了一种类型:多生产者、多消费者,且每一条消息只能被其中一个消费者接收,如果有这种需求,可以使用 async-channel 包 + +### watch channel + +watch channel 是一种可以发送多次消息的 channel。它的特点是可以有多个接收端,每个接收端都可以接收到发送端发送的最新消息。 + +watch channel 适用于以下场景: + +- 线程之间需要传递多次消息。 +- 线程之间需要订阅最新消息 + +### tokio::select! + +tokio::select! 宏允许我们等待多个异步的任务,并在其中一个完成时返回。 + +select! 最多可以支持 64 个分支. + +```shell +# 每个分支形式 +<模式> = => <结果处理>, +``` + +## 常见使用错误 + +### std::sync::Mutex 和 tokio::sync::Mutex + +一个常见的错误无条件地使用 tokio::sync::Mutex ,而真相是:Tokio 提供的异步锁只应该在跨多个 .await调用时使用,而且 Tokio 的 +Mutex 实际上内部使用的也是 std::sync::Mutex。 + +Tokio 提供的锁最大的优点就是:它可以在 .await 执行期间被持有,而且不会有任何问题。但是代价就是,这种异步锁的性能开销会更高 + +锁的使用有以下经验之谈: + +- 锁如果在多个 .await 过程中持有,应该使用 Tokio 提供的锁,原因是 + .await的过程中锁可能在线程间转移,若使用标准库的同步锁存在死锁的可能性,例如某个任务刚获取完锁,还没使用完就因为 .await + 让出了当前线程的所有权,结果下个任务又去获取了锁,造成死锁 +- 锁竞争不多的情况下,使用 std::sync::Mutex +- 锁竞争多,可以考虑使用三方库提供的性能更高的锁,例如 parking_lot::Mutex + +## 源码 + +### task 抽象 + +任务状态 + +```rust +// tokio-1.40.0/src/runtime/task/state.rs +pub(super) struct State { + val: AtomicUsize, +} +``` + +## 参考 + +- [探索 Tokio Runtime](https://juejin.cn/post/7307097620846837812) \ No newline at end of file diff --git a/chapter14-concurrecy/src/concurrency1_convar.rs b/chapter14-concurrecy/src/concurrency1_convar.rs new file mode 100644 index 0000000..0e23b04 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency1_convar.rs @@ -0,0 +1,56 @@ +mod concurrency2_future; + +use parking_lot::{Condvar, Mutex}; +use std::sync::Arc; +use std::thread; +use std::thread::sleep; +use std::time::Duration; + +// 我们启动三个线程,t1,t2,t3。分别执行任务T1,T2,T3。现在要求:T2必须等待T1和T3完成之后再执行 +pub fn main() { + let pair = Arc::new((Mutex::new(0), Condvar::new())); + let pair2 = pair.clone(); + let pair3 = pair.clone(); + + let t1 = thread::Builder::new() + .name("T1".to_string()) + .spawn(move || { + sleep(Duration::from_secs(4)); + println!("I'm working in T1, step 1"); + let &(ref lock, ref cvar) = &*pair2; + let mut started = lock.lock(); + *started += 2; + cvar.notify_one(); + }) + .unwrap(); + + let t2 = thread::Builder::new() + .name("T2".to_string()) + .spawn(move || { + println!("I'm working in T2, start"); + let &(ref lock, ref cvar) = &*pair; + let mut notify = lock.lock(); + + while *notify < 5 { + cvar.wait(&mut notify); + } + println!("I'm working in T2, final"); + }) + .unwrap(); + + let t3 = thread::Builder::new() + .name("T3".to_string()) + .spawn(move || { + sleep(Duration::from_secs(3)); + println!("I'm working in T3, step 2"); + let &(ref lock, ref cvar) = &*pair3; + let mut started = lock.lock(); + *started += 3; + cvar.notify_one(); + }) + .unwrap(); + + t1.join().unwrap(); + t2.join().unwrap(); + t3.join().unwrap(); +} diff --git a/chapter14-concurrecy/src/concurrency2_future.rs b/chapter14-concurrecy/src/concurrency2_future.rs new file mode 100644 index 0000000..71cec65 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency2_future.rs @@ -0,0 +1,28 @@ +use futures::executor::block_on; +use std::future::Future; + +#[tokio::main] +async fn main() { + let name1 = "Danny".to_string(); + let name2 = "Joy".to_string(); + + say_hello1(&name1).await; + say_hello2(&name2).await; + + // Future 除了可以用 await 来执行外,还可以直接用 executor 执行 + block_on(say_hello1(&name1)); + block_on(say_hello2(&name2)); +} + +async fn say_hello1(name: &str) -> usize { + println!("Hello {}", name); + 42 +} + +// async fn 关键字相当于一个返回 impl Future 的语法糖 +fn say_hello2<'fut>(name: &'fut str) -> impl Future + 'fut { + async move { + println!("Hello {}", name); + 42 + } +} diff --git a/chapter14-concurrecy/src/concurrency3_stream.rs b/chapter14-concurrecy/src/concurrency3_stream.rs new file mode 100644 index 0000000..37808b4 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency3_stream.rs @@ -0,0 +1,10 @@ +use tokio_stream::StreamExt; + +#[tokio::main] +async fn main() { + let mut stream = tokio_stream::iter(&[1, 2, 3]); + + while let Some(v) = stream.next().await { + println!("GOT = {:?}", v); + } +} diff --git a/chapter14-concurrecy/src/concurrency4_pin_stack.rs b/chapter14-concurrecy/src/concurrency4_pin_stack.rs new file mode 100644 index 0000000..f0186a0 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency4_pin_stack.rs @@ -0,0 +1,61 @@ +use std::marker::PhantomPinned; +use std::pin::Pin; + +#[derive(Debug)] +struct Test { + a: String, + b: *const String, + _marker: PhantomPinned, +} + +impl Test { + fn new(txt: &str) -> Self { + Test { + a: String::from(txt), + b: std::ptr::null(), + _marker: PhantomPinned, // 这个标记可以让我们的类型自动实现特征`!Unpin` + } + } + + fn init(self: Pin<&mut Self>) { + let self_ptr: *const String = &self.a; + let this = unsafe { self.get_unchecked_mut() }; // 一旦类型实现了 !Unpin ,那将它的值固定到栈( stack )上就是不安全的行为,因此在代码中我们使用了 unsafe 语句块来进行处理 + this.b = self_ptr; + } + + fn a(self: Pin<&Self>) -> &str { + &self.get_ref().a + } + + fn b(self: Pin<&Self>) -> &String { + assert!( + !self.b.is_null(), + "Test::b called without Test::init being called first" + ); + unsafe { &*(self.b) } + } +} + +pub fn main() { + // 此时的`test1`可以被安全的移动 + let mut test1 = Test::new("test1"); + // 新的`test1`由于使用了`Pin`,因此无法再被移动,这里的声明会将之前的`test1`遮蔽掉(shadow) + let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; + Test::init(test1.as_mut()); + + let mut test2 = Test::new("test2"); + let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; + Test::init(test2.as_mut()); + + println!( + "a: {}, b: {}", + Test::a(test1.as_ref()), + Test::b(test1.as_ref()) + ); + std::mem::swap(test1.get_mut(), test2.get_mut()); + println!( + "a: {}, b: {}", + Test::a(test2.as_ref()), + Test::b(test2.as_ref()) + ); +} diff --git a/chapter14-concurrecy/src/concurrency5_pin_heap.rs b/chapter14-concurrecy/src/concurrency5_pin_heap.rs new file mode 100644 index 0000000..046a8f9 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency5_pin_heap.rs @@ -0,0 +1,40 @@ +use std::marker::PhantomPinned; +use std::pin::Pin; + +#[derive(Debug)] +struct Test { + a: String, + b: *const String, + _marker: PhantomPinned, +} + +impl Test { + fn new(txt: &str) -> Pin> { + let t = Test { + a: String::from(txt), + b: std::ptr::null(), + _marker: PhantomPinned, + }; + let mut boxed = Box::pin(t); + let self_ptr: *const String = &boxed.as_ref().a; + unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr }; + + boxed + } + + fn a(self: Pin<&Self>) -> &str { + &self.get_ref().a + } + + fn b(self: Pin<&Self>) -> &String { + unsafe { &*(self.b) } + } +} + +pub fn main() { + let test1 = Test::new("test1"); + let test2 = Test::new("test2"); + + println!("a: {}, b: {}", test1.as_ref().a(), test1.as_ref().b()); + println!("a: {}, b: {}", test2.as_ref().a(), test2.as_ref().b()); +} diff --git a/chapter14-concurrecy/src/concurrency6_tokio_main.rs b/chapter14-concurrecy/src/concurrency6_tokio_main.rs new file mode 100644 index 0000000..3daf90a --- /dev/null +++ b/chapter14-concurrecy/src/concurrency6_tokio_main.rs @@ -0,0 +1,15 @@ +#[tokio::main] +async fn main() { + println!("hello"); +} +/* +fn main() { + tokio::runtime::Builder::new_multi_thread().enable_all() + .build().unwrap() + .block_on(async { + // async main + println!("hello"); + }) +} + +*/ diff --git a/chapter14-concurrecy/src/concurrency7_tokio_mpsc.rs b/chapter14-concurrecy/src/concurrency7_tokio_mpsc.rs new file mode 100644 index 0000000..cdaaf0e --- /dev/null +++ b/chapter14-concurrecy/src/concurrency7_tokio_mpsc.rs @@ -0,0 +1,20 @@ +use tokio::sync::mpsc; + +#[tokio::main] +async fn main() { + // mpsc 多生产者,单消费者模式 + let (sx, mut rx) = mpsc::channel(32); + let sx2 = sx.clone(); + + tokio::spawn(async move { + sx.send("sending from first handle").await; + }); + + tokio::spawn(async move { + sx2.send("sending from second handle").await; + }); + + while let Some(message) = rx.recv().await { + println!("GOT = {}", message); + } +} diff --git a/chapter14-concurrecy/src/concurrency8_tokio_watch.rs b/chapter14-concurrecy/src/concurrency8_tokio_watch.rs new file mode 100644 index 0000000..7c7b376 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency8_tokio_watch.rs @@ -0,0 +1,28 @@ +use tokio::sync::watch; +use tokio::time::{sleep, Duration}; + +#[tokio::main] +async fn main() { + // watch 单一生产者、多消费者的通道,只保留最后发送的值 + let (tx, mut rx) = watch::channel("hello"); + let mut rx2 = rx.clone(); + + tokio::spawn(async move { + while rx.changed().await.is_ok() { + println!("received1 = {:?}", *rx.borrow()); + } + }); + + tokio::spawn(async move { + while rx2.changed().await.is_ok() { + println!("received2 = {:?}", *rx2.borrow()); + } + }); + + tx.send("first").expect("send error!"); + sleep(Duration::from_millis(3000)).await; + tx.send("second").expect("send error!"); + tx.send("third").expect("send error!"); + + sleep(Duration::from_millis(3000)).await; +} diff --git a/chapter14-concurrecy/src/concurrency9_tokio_select.rs b/chapter14-concurrecy/src/concurrency9_tokio_select.rs new file mode 100644 index 0000000..f681cb5 --- /dev/null +++ b/chapter14-concurrecy/src/concurrency9_tokio_select.rs @@ -0,0 +1,24 @@ +use tokio::sync::oneshot; + +#[tokio::main] +async fn main() { + let (tx1, rx1) = oneshot::channel(); + let (tx2, rx2) = oneshot::channel(); + + tokio::spawn(async move { + let _ = tx1.send("one"); + }); + + tokio::spawn(async move { + let _ = tx2.send("two"); + }); + + tokio::select! {// select! 语句在两个channels上等待,并将va1绑定到任务返回的值上. 当其中任一 tx1 或者 tx2 完成时,与之相关的块就会执行. + val = rx1 => { + println!("rx1 completed first with {:?}", val); + } + val = rx2 => { + println!("rx2 completed first with {:?}", val); + } + } +} diff --git a/chapter14-concurrecy/src/reactor.png b/chapter14-concurrecy/src/reactor.png new file mode 100644 index 0000000..6812765 Binary files /dev/null and b/chapter14-concurrecy/src/reactor.png differ diff --git a/chapter14-concurrecy/src/runtime-scheduler.png b/chapter14-concurrecy/src/runtime-scheduler.png new file mode 100644 index 0000000..4ed49f7 Binary files /dev/null and b/chapter14-concurrecy/src/runtime-scheduler.png differ diff --git a/chapter14-concurrecy/src/tokio_and_run_queue.png b/chapter14-concurrecy/src/tokio_and_run_queue.png new file mode 100644 index 0000000..8842d2e Binary files /dev/null and b/chapter14-concurrecy/src/tokio_and_run_queue.png differ diff --git a/chapter15-macro/Cargo.toml b/chapter15-macro/Cargo.toml new file mode 100644 index 0000000..9a7834a --- /dev/null +++ b/chapter15-macro/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "chapter15-macro" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +# 链接 rustc 工具链提供的 proc-macro 库 libproc_macro, 同时也表明该 crate 是 proc macro 类型 +proc-macro = true + + +[dependencies] +anyhow = "1" +askama = "0.11" # 处理 jinjia 模板,模板需要放在和 src 平行的 templates 目录下 +darling = "0.13" # 可以很方便的处理宏里面 attributes +proc-macro2 = "1" # proc-macro 的封装 +quote = "1.0" # 提供 quote!{} 宏来生成代码(如实现 trait) +syn = { version = "1", features = ["extra-traits"] } # 解析 TokenStream 来生成语法树 AST ,使用 extra-traits 可以用于 Debug + + + + + + +[[example]] +name = "macro1" +path = "examples/macro1-declarativemacro.rs" + + +[[example]] +name = "macro2" +path = "examples/macro2-declarativemacro-hygiene.rs" + + +[[example]] +name = "macro3" +path = "examples/macro3-derive-macro.rs" + + +[[example]] +name = "macro4" +path = "examples/macro4-attribute-macro.rs" \ No newline at end of file diff --git a/chapter15-macro/examples/macro1-declarativemacro.rs b/chapter15-macro/examples/macro1-declarativemacro.rs new file mode 100644 index 0000000..4b6bccc --- /dev/null +++ b/chapter15-macro/examples/macro1-declarativemacro.rs @@ -0,0 +1,48 @@ +#[macro_export] +macro_rules! my_vec { + // 匹配 my_vec![] + () => { + std::vec::Vec::new() + }; + // 匹配 my_vec![1,2,3] + ($($el:expr), *) => { + // 这段代码需要用{}包裹起来,因为宏需要展开,这样能保证作用域正常,不影响外部。这也是rust的宏是 Hygienic Macros 的体现。 + // 而 C/C++ 的宏不强制要求,但是如果遇到代码片段,在 C/C++ 中也应该使用{}包裹起来。 + { + let mut v = std::vec::Vec::new(); + $(v.push($el);)* // 由于匹配的时候匹配到一个 $(...)* (我们可以不管分隔符),在执行的代码块中,我们也要相应地使用 $(...)* 展开。所以这句 $(v.push($el);)* 相当于匹配出多少个 $el就展开多少句 push 语句。 + v + } + }; + // 匹配 my_vec![1; 3] + ($el:expr; $n:expr) => { + std::vec::from_elem($el, $n) + }; +} + +fn main() { + println!("{:?}", my_vec![1, 2, 3]); +} + +/* +✗ cargo expand --package chapter15-macro --example macro1 + + +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use chapter15_macro::my_vec; +fn main() { + { + ::std::io::_print( + format_args!( + "{0:?}\n", + { + let mut v = std::vec::Vec::new(); + v.push(1); + v.push(2); + v.push(3); + + */ diff --git a/chapter15-macro/examples/macro2-declarativemacro-hygiene.rs b/chapter15-macro/examples/macro2-declarativemacro-hygiene.rs new file mode 100644 index 0000000..c8f4b22 --- /dev/null +++ b/chapter15-macro/examples/macro2-declarativemacro-hygiene.rs @@ -0,0 +1,39 @@ +#[macro_export] +macro_rules! make_local { + () => { + let local = 0; + }; +} + +fn main() { + let local = 42; + make_local!(); + assert_eq!(local, 42); +} + +/* +✗ cargo expand --package chapter15-macro --example macro2 + + +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +fn main() { + let local = 42; + let local = 0; + match (&local, &42) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + */ diff --git a/chapter15-macro/examples/macro3-derive-macro.rs b/chapter15-macro/examples/macro3-derive-macro.rs new file mode 100644 index 0000000..b77a40d --- /dev/null +++ b/chapter15-macro/examples/macro3-derive-macro.rs @@ -0,0 +1,30 @@ +use chapter15_macro::Builder; + +#[allow(dead_code)] +#[derive(Debug, Builder)] +pub struct Command { + executable: String, + args: Vec, + env: Vec, + current_dir: Option, +} + +fn main() { + let command = Command::builder() + .executable("cargo".to_owned()) + .args(vec!["build".to_owned(), "--release".to_owned()]) + .env(vec![]) + .build() + .unwrap(); + assert!(command.current_dir.is_none()); + + let command = Command::builder() + .executable("cargo".to_owned()) + .args(vec!["build".to_owned(), "--release".to_owned()]) + .env(vec![]) + .current_dir("..".to_owned()) + .build() + .unwrap(); + assert!(command.current_dir.is_some()); + println!("{:?}", command); +} diff --git a/chapter15-macro/examples/macro4-attribute-macro.rs b/chapter15-macro/examples/macro4-attribute-macro.rs new file mode 100644 index 0000000..59b3ee4 --- /dev/null +++ b/chapter15-macro/examples/macro4-attribute-macro.rs @@ -0,0 +1,16 @@ +use core::time; +use std::thread; + +use chapter15_macro::log_bench; + +#[log_bench] +fn test_my_macro() { + for num in 0..3 { + thread::sleep(time::Duration::from_secs(1)); + println!("{}", num); + } +} + +fn main() { + test_my_macro(); +} diff --git a/chapter15-macro/macro.md b/chapter15-macro/macro.md new file mode 100644 index 0000000..06a1de1 --- /dev/null +++ b/chapter15-macro/macro.md @@ -0,0 +1,315 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [macro 宏](#macro-%E5%AE%8F) + - [声明宏(declarative macro)](#%E5%A3%B0%E6%98%8E%E5%AE%8Fdeclarative-macro) + - [声明宏的卫生性 hygiene](#%E5%A3%B0%E6%98%8E%E5%AE%8F%E7%9A%84%E5%8D%AB%E7%94%9F%E6%80%A7-hygiene) + - [过程宏(procedural macro)](#%E8%BF%87%E7%A8%8B%E5%AE%8Fprocedural-macro) + - [派生宏(derive macro 可推导宏)](#%E6%B4%BE%E7%94%9F%E5%AE%8Fderive-macro-%E5%8F%AF%E6%8E%A8%E5%AF%BC%E5%AE%8F) + - [属性宏(attribute macro)](#%E5%B1%9E%E6%80%A7%E5%AE%8Fattribute-macro) + - [第三方实现 --> #[tokio::main]](#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%AE%9E%E7%8E%B0----tokiomain) + - [函数宏(function-like macro)](#%E5%87%BD%E6%95%B0%E5%AE%8Ffunction-like-macro) + - [其他编程语言常见的元编程方式](#%E5%85%B6%E4%BB%96%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E5%B8%B8%E8%A7%81%E7%9A%84%E5%85%83%E7%BC%96%E7%A8%8B%E6%96%B9%E5%BC%8F) + - [工具](#%E5%B7%A5%E5%85%B7) + - [cargo-expand](#cargo-expand) + - [syn](#syn) + - [quote](#quote) + - [参考](#%E5%8F%82%E8%80%83) + + + +# macro 宏 + +元编程可以让开发者将原生语言写的代码作为数据输入,经过自定义的逻辑,重新输出为新的代码并作为整体代码的一部分。这个过程一般在编译时期完成(对于编译型语言来说),所以让人觉得这是一种神奇的 +“黑魔法 + +宏就两大类:对代码模板做简单替换的声明宏(declarative macro)、可以深度定制和生成代码的过程宏(procedural macro)。 + +宏调用有三种等价的形式:marco!(xx), marcro![xxx], macro!{xx}。惯例是: + +- 函数传参调用场景使用 () 形式,如 println!(); +- 字面量初始化使用 [] 形式,如 vec![0; 4]; + +## 声明宏(declarative macro) + +声明宏可以用 macro_rules! 来描述,比如像 vec![]、println!、以及 info!,它们都是声明宏。 + +声明式宏类似于 match 匹配。它可以将表达式的结果与多个模式进行匹配。一旦匹配成功,那么该模式相关联的代码将被展开。和 match +不同的是,宏里的值是一段 rust 源代码。所有这些都发生在编译期,并没有运行期的性能损耗 + +```rust +#[cfg(all(not(no_global_oom_handling), not(test)))] +#[macro_export] +#[stable(feature = "rust1", since = "1.0.0")] +#[rustc_diagnostic_item = "vec_macro"] +#[allow_internal_unstable(rustc_attrs, liballoc_internals)] +macro_rules! vec { + () => ( + $crate::__rust_force_expr!($crate::vec::Vec::new()) + ); + // 匹配到以 ; 分隔的两个表达式,; 左边的表达式的值将被捕获匹配到 $elem,; 右边的表达式的值将被捕获匹配到 $n + ($elem:expr; $n:expr) => ( + $crate::__rust_force_expr!($crate::vec::from_elem($elem, $n)) + ); + ($($x:expr),+ $(,)?) => ( + $crate::__rust_force_expr!(<[_]>::into_vec( + // This rustc_box is not required, but it produces a dramatic improvement in compile + // time when constructing arrays with many elements. + #[rustc_box] + $crate::boxed::Box::new([$($x),+]) + )) + ); +} +``` + +- $crate 是一个特殊的元变量,用来指代当前 crate +- 条件捕获的参数使用 $ 开头的标识符来声明 +- #[macro_export]标签是用来声明:只要 use 了这个crate,就可以使用该宏。同时包含被 export 出的宏的模块,在声明时必须放在前面,否则靠前的模块里找不到这些宏 + +macro_rules! 的基本结构 + +```shell +macro_rules! $ name { + $ rule0; + $ rule1; + //... + $ ruleN; +} +``` + +每一条 rule 其实就是模式匹配和代码扩展生成: + +```shell +( $matcher ) => { $expansion }; +``` + +类似 vec![0; 10] 的功能时,0; 10 ,其中 ; 左边是元素初始值 0,; 右边是个数 10。那么匹配似乎可以为: + +```shell +($elem ; $n) => { ... } +``` + +描述是不精确的,我们还需要加上捕获方式,即捕获的是一个表达式 + +```shell +($elem:expr ; $n:expr) => { ... } +``` + +```rust +let v = vec![1, 2, 3]; +``` + +先看 $matcher 部分,即 1, 2, 3。像这种需要匹配一系列 token 的模式,我们需要使用宏里的重复匹配模式。比如要想匹配 1,2,3,可以写成: + +```rust +( $ ( $ elem:expr ), * ) => { ... } +``` + +即 $(...),* 模式,而 (...) 则是和上节中变量捕获的方式是一样的,即 $elem:expr。, 表示为分隔符,* 表示匹配 0 或者多次. + +为参数明确类型,哪些类型可用也整理在这里了: + +- item,比如一个函数、结构体、模块等。 +- block,代码块。比如一系列由花括号包裹的表达式和语句。 +- stmt,语句。比如一个赋值语句。 +- pat,模式。 +- expr,表达式。刚才的例子使用过了。ty,类型。比如 Vec。 +- ident,标识符。比如一个变量名。path,路径。比如:foo、::std::mem::replace、transmute::<_, int>。meta,元数据。一般是在 #[...] + 和 #![...] 属性内部的数据。 +- tt,单个的 token 树(一个独立的 token 或一系列在匹配完整的定界符 ()、[] 或 {} 中的 token)。 +- vis,可能为空的一个 Visibility 修饰符。比如 pub、pub(crate) + +### 声明宏的卫生性 hygiene + +宏的卫生性,其实说的就是宏在上下文工作不影响或不受周围环境的影响。或者换句话来说,就是宏的调用是没有 side effect。对于 +macro_rules!, +它是部分卫生的(partially hygienic)。我们目前阶段可以不用太关注 macro_rules! 在哪些场景是 “不卫生” 的,而是了解一下 +macro_rules! 是如何在大多数场景做到 “卫生” 的 + +```rust +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use chapter15_macro::make_local; +fn main() { + let local = 42; + let local = 0; + match (&local, &42) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; +} +``` + +Rust 看来,macro_rules 中的 local 和 main() 里的 local 分别有着不同的颜色,所以不会将其混淆 + +## 过程宏(procedural macro) + +过程宏分为三种: + +- 函数宏(function-like macro):custom!(…) 看起来像函数的宏,但在编译期进行处理。比如 sqlx 里的 query 宏,它内部展开出一个 + expand_query + 函数宏。你可能想象不到,看上去一个简单的 query 处理,内部有多么庞大的代码结构。 +- 属性宏(attribute macro):#[CustomAttribute]可以在其他代码块上添加属性,为代码块提供更多功能。比如 rocket 的 get / put + 等路由属性,#[tokio::main] 来引入 runtime。 +- 派生宏(derive macro 可推导宏):为 derive 属性添加新的功能,一般用来为 struct/enum/union 实现特定的 + trait。这是我们平时使用最多的宏,比如 #[derive(Debug)] 为我们的数据结构提供 + Debug trait + 的实现、#[derive(Serialize, Deserialize)]为我们的数据结构提供 serde 相关 trait 的实现 + +它更像函数,他接受一些代码作为参数输入,然后对他们进行加工,生成新的代码,他不是在做声明式宏那样的模式匹配 + +不能在原始的crate中直接写过程式宏,需要把过程式宏放到一个单独的crate中(以后可能会消除这种约定)。定义过程式宏的方法如下 + +```rust +use proc_macro; + +#[some_attribute] +pub fn some_name(input: TokenStream) -> TokenStream {} + +``` + +在单独的 crate package 中定义过程宏的原因: + +proc macro 定义需要先被编译器编译为 host 架构类型,后续编译使用它的代码时,编译器才能 dlopen 和执行它们来为 target 架构生成代码; +非过程宏 crate 需要被边翼卫 target 架构类型,然后才能被和其它 target 架构的动态库链接; + +需要引入proc_macro 这个 crate,然后标签是用来声明它是哪种过程式宏的,接着就是一个函数定义,函数接受 TokenStream,返回 +TokenStream。TokenStream 类型就定义在 proc_macro 包中,表示 token 序列。 + +除了标准库 proc_macro 中的这个包,还可以使用proc_macro2 包,使用 proc_macro2::TokenStream::from() 和 proc_macro:: +TokenStream::from() +可以很便捷地在两个包的类型间进行转换。 +使用 proc_macro2 的好处是可以在过程宏外部使用 proc_macro2 的类型,相反 proc_macro 中的类型只可以在过程宏的上下文中使用。 +且 proc_macro2 写出的宏更容易编写测试代码。 + +### 派生宏(derive macro 可推导宏) + +派生宏可以自动生成实现特定trait的代码,减少手动实现的繁琐性。 + +```rust +#[derive(Debug)] +struct Person { + name: String, + age: u32, +} +``` + +#[derive(Debug)]是一个派生宏,它告诉Rust编译器为Person结构体自动生成Debug trait的实现 + +### 属性宏(attribute macro) + +在Rust中,属性宏是一种特殊的宏,它允许开发者在代码上方添加自定义的属性,并在编译期间对代码进行处理。属性宏使用proc_macro_attribute属性来定义 + +```rust +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn attribute_macro(attr: TokenStream, item: TokenStream) -> TokenStream { + // 宏的处理逻辑 + // ... +} + +``` + +使用proc_macro_attribute属性来定义了一个名为attribute_macro的属性宏。 +属性宏接受两个TokenStream参数:attr表示属性的输入,item表示应用该属性的代码块。在宏的处理逻辑中,我们可以根据attr和item对代码进行定制化处理,并返回一个TokenStream作为输出。 + +#### 第三方实现 --> #[tokio::main] + +```rust +// tokio-macros-2.4.0/src/entry.rs +fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenStream { + // ... + + let mut rt = match config.flavor { + RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=> + #crate_path::runtime::Builder::new_current_thread() + }, + RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=> + #crate_path::runtime::Builder::new_multi_thread() + }, + }; + // ... + + let generated_attrs = if is_test { + quote! { + #[::core::prelude::v1::test] + } + } else { + quote! {} + }; + + let body_ident = quote! { body }; + let last_block = quote_spanned! {last_stmt_end_span=> + #[allow(clippy::expect_used, clippy::diverging_sub_expression)] + { + return #rt + .enable_all() + .build() + .expect("Failed building the Runtime") + .block_on(#body_ident); + } + }; + + let body = input.body(); + + // .. + + input.into_tokens(generated_attrs, body, last_block) +} +``` + +### 函数宏(function-like macro) + +## 其他编程语言常见的元编程方式 + +- Go 的 ast 包和 go generate 机制:Go 没有显示提供元编程的相应机制,转而提供了一些相对不那么优雅的机制来实现类似于元编程的效果。比如 + ast 包可以暴露 Go 程序的语法树,从而让开发者可在编译时期对源代码进行修改或者根据模版生成其他类型代码。 +- C++ 的 Template 编程:据说 C++ 的 Template 编程是图灵完备的,可在编译时期完成很多让人瞠目结舌的逻辑。由于 C++ 的 Template + 编程非常复杂且难以掌握,所以易用性非常差。 +- C 语言的宏:这估计是大多数程序员对于宏的最初体验。个人觉得, C 语言中的宏本质上是发生在预处理过程的文本替换,是一种非常简单原始的元编程机制。而正是这种原始能力,导致 + C 语言的宏结合编译器的各种扩展充满了各种奇技淫巧,可读性和可调试性都非常差,而且稍不小心就很容易写出错误的宏 + +## 工具 + +### cargo-expand + +https://github.com/dtolnay/cargo-expand + +一个Rust cargo子命令扩展,通过简单的 cargo expand 命令,你可以获取当前项目中所有源码经过宏展开后的结果 + +```shell +# 直接安装 cargo-expand 插件 +$ cargo install cargo-expand +``` + +### syn + +syn 是一个对 TokenStream 解析的库,它提供了丰富的数据结构,对语法树中遇到的各种 Rust 语法都有支持。 + +比如一个 Struct 结构,在 TokenStream 中,看到的就是一系列 TokenTree,而通过 syn 解析后,struct +的各种属性以及它的各个字段,都有明确的类型。这样,我们可以很方便地通过模式匹配来选择合适的类型进行对应的处理。 + +### quote + +quote 是一个特殊的原语,它把代码转换成可以操作的数据(代码即数据) + +## 参考 + +- [Rust 的声明宏机制](https://www.cnblogs.com/RioTian/p/18130417) +- [Rust宏及声明式宏项目MacroKata](https://forsworns.github.io/zh/blogs/20210224/) \ No newline at end of file diff --git a/chapter15-macro/src/builder.rs b/chapter15-macro/src/builder.rs new file mode 100644 index 0000000..4affcdc --- /dev/null +++ b/chapter15-macro/src/builder.rs @@ -0,0 +1,160 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, GenericArgument, Path, Type, + TypePath, +}; + +/// 我们需要的描述一个字段的所有信息 +struct Fd { + name: Ident, + ty: Type, + optional: bool, +} + +/// 我们需要的描述一个 struct 的所有信息 +pub struct BuilderContext { + name: Ident, + fields: Vec, +} + +/// 把一个 Field 转换成 Fd +impl From for Fd { + fn from(f: Field) -> Self { + let (optional, ty) = get_option_inner(&f.ty); + Self { + // 此时,我们拿到的是 NamedFields,所以 ident 必然存在 + name: f.ident.unwrap(), + optional, + ty: ty.to_owned(), + } + } +} + +/// 把 DeriveInput 转换成 BuilderContext +impl From for BuilderContext { + fn from(input: DeriveInput) -> Self { + let name = input.ident; + + let fields = if let Data::Struct(DataStruct { + fields: Fields::Named(FieldsNamed { named, .. }), + .. + }) = input.data + { + named + } else { + panic!("Unsupported data type"); + }; + + let fds = fields.into_iter().map(Fd::from).collect(); + Self { name, fields: fds } + } +} + +impl BuilderContext { + pub fn render(&self) -> TokenStream { + let name = &self.name; + // 生成 XXXBuilder 的 ident + let builder_name = Ident::new(&format!("{}Builder", name), name.span()); + + let optionized_fields = self.gen_optionized_fields(); + let methods = self.gen_methods(); + let assigns = self.gen_assigns(); + + quote! { + /// Builder 结构 + #[derive(Debug, Default)] + struct #builder_name { + #(#optionized_fields,)* + } + + /// Builder 结构每个字段赋值的方法,以及 build() 方法 + impl #builder_name { + #(#methods)* + + pub fn build(mut self) -> Result<#name, &'static str> { + Ok(#name { + #(#assigns,)* + }) + } + } + + /// 为使用 Builder 的原结构提供 builder() 方法,生成 Builder 结构 + impl #name { + fn builder() -> #builder_name { + Default::default() + } + } + } + } + + // 为 XXXBuilder 生成 Option 字段 + // 比如:executable: String -> executable: Option + fn gen_optionized_fields(&self) -> Vec { + self.fields + .iter() + .map(|Fd { name, ty, .. }| quote! { #name: std::option::Option<#ty> }) + .collect() + } + + // 为 XXXBuilder 生成处理函数 + // 比如:methods: fn executable(mut self, v: impl Into) -> Self { self.executable = Some(v); self } + fn gen_methods(&self) -> Vec { + self.fields + .iter() + .map(|Fd { name, ty, .. }| { + quote! { + pub fn #name(mut self, v: impl Into<#ty>) -> Self { + self.#name = Some(v.into()); + self + } + } + }) + .collect() + } + + // 为 XXXBuilder 生成相应的赋值语句,把 XXXBuilder 每个字段赋值给 XXX 的字段 + // 比如:#field_name: self.#field_name.take().ok_or(" xxx need to be set!") + fn gen_assigns(&self) -> Vec { + self.fields + .iter() + .map(|Fd { name, optional, .. }| { + if *optional { + return quote! { + #name: self.#name.take() + }; + } + + quote! { + #name: self.#name.take().ok_or(concat!(stringify!(#name), " needs to be set!"))? + } + }) + .collect() + } +} + +// 如果是 T = Option,返回 (true, Inner);否则返回 (false, T) +fn get_option_inner(ty: &Type) -> (bool, &Type) { + // 首先模式匹配出 segments + if let Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) = ty + { + if let Some(v) = segments.iter().next() { + if v.ident == "Option" { + // 如果 PathSegment 第一个是 Option,那么它内部应该是 AngleBracketed,比如 + // 获取其第一个值,如果是 GenericArgument::Type,则返回 + let t = match &v.arguments { + syn::PathArguments::AngleBracketed(a) => match a.args.iter().next() { + Some(GenericArgument::Type(t)) => t, + _ => panic!("Not sure what to do with other GenericArgument"), + }, + _ => panic!("Not sure what to do with other PathArguments"), + }; + return (true, t); + } + } + } + (false, ty) +} diff --git a/chapter15-macro/src/lib.rs b/chapter15-macro/src/lib.rs new file mode 100644 index 0000000..e487525 --- /dev/null +++ b/chapter15-macro/src/lib.rs @@ -0,0 +1,43 @@ +// 引入 proc_macro crate,用于创建过程宏 +extern crate proc_macro; +use proc_macro::TokenStream; +use syn; +// 引入 syn crate,用于解析 Rust 代码 +use syn::{parse_macro_input, DeriveInput}; +// 引入 quote crate,用于生成代码片段 +use quote::quote; + +mod builder; + +// 定义 Builder 派生宏 +#[proc_macro_derive(Builder)] +pub fn derive_builder(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + builder::BuilderContext::from(input).render().into() +} + +// 定义 log_bench 属性宏 +#[proc_macro_attribute] +pub fn log_bench(_: TokenStream, item: TokenStream) -> TokenStream { + // 解析传入的函数定义 + let input_fn = parse_macro_input!(item as syn::ItemFn); + + // 获取函数名和函数体 + let fn_name = &input_fn.sig.ident; + let fn_block = &input_fn.block; + + // 构建新的函数定义,包含性能日志输出 + let expanded = quote! { + fn #fn_name() { + // 获取函数执行开始时间点 + let start = std::time::Instant::now(); + println!("进入函数: {}", stringify!(#fn_name)); + // 执行原函数体 + #fn_block + println!("离开函数: {} (耗时 {} ms)", stringify!(#fn_name), start.elapsed().as_millis()); + } + }; + + // 将生成的代码片段转换为 TokenStream,以便返回 + TokenStream::from(expanded) +}