├── .github └── workflows │ └── ci.yml ├── .gitignore ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── tailcall-impl ├── Cargo.toml └── src │ ├── helpers.rs │ ├── lib.rs │ └── transforms.rs └── tailcall ├── Cargo.toml ├── benches └── bench.rs ├── src ├── lib.rs └── trampoline.rs └── tests ├── correctness.rs ├── correctness_option.rs ├── correctness_result.rs └── stack_depth.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | bench: 17 | name: ⏱️ Benchmark 18 | runs-on: ubuntu-latest 19 | 20 | needs: 21 | - build 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - uses: Swatinem/rust-cache@v2.7.3 26 | - name: Run benchmarks 27 | run: cargo bench --all 28 | 29 | build: 30 | name: 🔨 Build 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | - uses: Swatinem/rust-cache@v2.7.3 36 | - name: Build (release) 37 | run: cargo build --all --release 38 | 39 | fmt: 40 | name: ✨ Check Formatting 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v3 45 | - uses: Swatinem/rust-cache@v2.7.3 46 | - name: Run Rustfmt 47 | run: cargo fmt --all -- --check 48 | 49 | lint: 50 | name: 🧹 Lint 51 | runs-on: ubuntu-latest 52 | 53 | steps: 54 | - uses: actions/checkout@v3 55 | - uses: Swatinem/rust-cache@v2.7.3 56 | - name: Run Clippy 57 | run: cargo clippy --all 58 | 59 | test: 60 | name: 🧪 Test 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - uses: actions/checkout@v3 65 | - uses: Swatinem/rust-cache@v2.7.3 66 | - name: Run tests 67 | run: cargo test --all 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyrights in the Tailcall project are retained by their contributors. No 2 | copyright assignment is required to contribute to the Tailcall project. 3 | 4 | For full authorship information, see the version control history. 5 | 6 | Except as otherwise noted (below and/or in individual files), Tailcall is 7 | licensed under the Apache License, Version 2.0 or 8 | or the MIT license 9 | or , at your option. 10 | 11 | The Tailcall project includes code from the Rust project 12 | published under these same licenses. 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "tailcall", 5 | "tailcall-impl", 6 | ] 7 | 8 | [workspace.package] 9 | description = "Safe, zero-cost tail recursion" 10 | documentation = "https://docs.rs/tailcall" 11 | repository = "https://github.com/alecdotninja/tailcall" 12 | readme = "README.md" 13 | keywords = ["recursion", "tail", "tailcall", "become"] 14 | categories = ["algorithms", "no-std"] 15 | license = "MIT OR Apache-2.0" 16 | authors = ["Alec Larsen "] 17 | edition = "2018" 18 | 19 | # ⚠️ Warning! When updating the version here, you must also update the version 20 | # in the `workspace.dependencies` section of this file for the `tailcall` and 21 | # `tailcall-impl` crates. 22 | version = "1.0.1" 23 | 24 | [workspace.dependencies] 25 | tailcall = { path = "tailcall", version = "1.0.1" } 26 | tailcall-impl = { path = "tailcall-impl", version = "1.0.1" } -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | rand/LICENSE-APACHE at master · rust-random/rand · GitHub 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 | Skip to content 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 429 | 430 |
431 | 432 |
433 | 434 | 435 |
436 | 437 |
438 | 439 | 440 | 441 |
442 |
443 |
444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 |
462 |
463 | 464 | 504 | 505 |

506 | 507 | /rand 510 | 511 | 512 |

513 | 514 |
515 | 516 | 563 | 564 | 607 | 608 | 609 |
610 |
611 |
612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | Permalink 621 | 622 | 623 | 636 | 637 | 638 |
639 | 640 | 641 |
642 | 645 | Branch: 646 | master 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 |
655 | 656 |
657 | 661 | Find file 662 | 663 | 664 | Copy path 665 | 666 |
667 |
668 | 671 | 672 |
673 | 677 | Find file 678 | 679 | 680 | Copy path 681 | 682 |
683 |
684 | 685 | 686 | 687 | 688 | 689 | 690 |
691 |
692 | 693 | @GabrielMajeri 694 | GabrielMajeri 695 | 696 | Fix license link 697 | 698 | 699 | 700 | 570fd8b 701 | Jan 9, 2018 702 | 703 |
704 | 705 |
706 |
707 | 708 | 2 contributors 709 | 710 | 714 |
715 | 718 |

719 | Users who have contributed to this file 720 |

721 |
722 | 723 |
724 |
725 | 726 | 727 | @GabrielMajeri 728 | 729 | @huonw 730 | 731 | 732 | 733 |
734 |
735 | 736 | 737 | 738 | 739 | 740 |
741 | 742 |
743 |
744 | 745 | 201 lines (169 sloc) 746 | 747 | 10.6 KB 748 |
749 | 750 |
751 | 752 |
753 | Raw 754 | Blame 755 | History 756 |
757 | 758 | 759 |
760 | 761 | 765 | 769 |
770 |
771 |
772 | 773 | 774 | 775 | 776 | 777 | 778 |
779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1412 | 1413 | 1414 | 1415 | 1416 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1423 | 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 | 1433 | 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1461 | 1462 | 1463 | 1464 | 1465 | 1466 | 1467 | 1468 | 1469 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | 1568 | 1570 | 1571 | 1572 | 1573 | 1574 | 1575 | 1576 | 1577 | 1578 | 1579 | 1580 | 1581 | 1582 | 1583 | 1584 | 1585 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | 1593 | 1594 | 1596 | 1597 | 1598 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | 1605 | 1606 | 1607 | 1608 | 1609 | 1610 | 1611 | 1612 | 1613 | 1614 | 1615 | 1616 | 1617 |
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
796 |
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
805 |
1. Definitions.
814 |
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
827 |
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
840 |
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
873 |
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
886 |
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
903 |
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
924 |
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
945 |
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
978 |
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
1035 |
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
1052 |
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
1081 |
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
1146 |
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
1167 |
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
1180 |
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
1193 |
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
1218 |
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
1287 |
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
1316 |
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
1349 |
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
1370 |
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
1411 |
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
1460 |
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
1505 |
END OF TERMS AND CONDITIONS
1514 |
APPENDIX: How to apply the Apache License to your work.
1523 |
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
1560 |
Copyright [yyyy] [name of copyright owner]
1569 |
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
1586 |
https://www.apache.org/licenses/LICENSE-2.0
1595 |
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
1618 | 1619 | 1640 | 1641 |
1642 | 1643 |
1644 | 1645 | 1646 | 1647 |
1648 | 1649 | 1650 |
1651 | 1652 | 1653 |
1654 |
1655 | 1656 | 1657 | 1658 |
1659 |
1660 | 1661 |
1662 |
1663 | 1664 | 1665 |
1666 | 1667 | 1668 | 1696 | 1697 | 1698 | 1699 |
1700 | 1701 | 1704 | You can’t perform that action at this time. 1705 |
1706 | 1707 | 1708 | 1709 | 1710 | 1711 | 1712 | 1713 | 1714 | 1715 | 1721 | 1732 | 1733 | 1737 | 1738 |
1739 | 1740 | 1741 | 1742 | 1743 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 Developers of the Rand project 2 | Copyright (c) 2014 The Rust Project Developers 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailcall 2 | 3 | [![CI](https://github.com/alecdotninja/tailcall/actions/workflows/ci.yml/badge.svg)](https://github.com/alecdotninja/tailcall/actions/workflows/ci.yml) 4 | [![Current Crates.io Version](https://img.shields.io/crates/v/tailcall.svg)](https://crates.io/crates/tailcall) 5 | [![Docs](https://docs.rs/tailcall/badge.svg)](https://docs.rs/tailcall) 6 | 7 | Tailcall is a library that adds safe, zero-cost [tail recursion](https://en.wikipedia.org/wiki/Tail_call) to stable Rust. 8 | 9 | Eventually, it will be superseded by the [`become` keyword](https://internals.rust-lang.org/t/pre-rfc-explicit-proper-tail-calls/3797/16). 10 | 11 | ## Installation 12 | 13 | Tailcall is distributed as a [crate](https://crates.io/crates/tailcall). 14 | 15 | Add this to your `Cargo.toml`: 16 | 17 | ```toml 18 | [dependencies] 19 | tailcall = "~1" 20 | ``` 21 | 22 | ## Usage 23 | 24 | Add the `tailcall` attribute to functions which you would like to use tail recursion: 25 | 26 | ```rust 27 | use tailcall::tailcall; 28 | 29 | #[tailcall] 30 | fn gcd(a: u64, b: u64) -> u64 { 31 | if b == 0 { 32 | a 33 | } else { 34 | gcd(b, a % b) 35 | } 36 | } 37 | ``` 38 | 39 | For more detailed information (including some limitations), please see [the docs](https://docs.rs/tailcall). 40 | 41 | ## Implementation 42 | 43 | The core idea is to rewrite the function into a loop using the [trampoline approach](https://en.wikipedia.org/wiki/Tail_call#Through_trampolining). 44 | Here is the (slightly reformatted) expansion for the `gcd` example above: 45 | 46 | ```rust 47 | fn gcd(a: u64, b: u64) -> u64 { 48 | tailcall::trampoline::run( 49 | #[inline(always)] |(a, b)| { 50 | tailcall::trampoline::Finish({ 51 | if b == 0 { 52 | a 53 | } else { 54 | return tailcall::trampoline::Recurse((b, a % b)) 55 | } 56 | }) 57 | }, 58 | (a, b), 59 | ) 60 | } 61 | ``` 62 | 63 | You can view the exact expansion for the `tailcall` macro in your use-case with `cargo expand`. 64 | 65 | ## Development 66 | 67 | Development dependencies, testing, documentation generation, packaging, and distribution are all managed via [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html). 68 | 69 | After checking out the repo, run `cargo test` to verify the test suite. 70 | The latest documentation can be generated with `cargo doc`. 71 | Before commiting, please make sure code is formatted canonically with `cargo fmt` and passes all lints with `cargo clippy`. 72 | New versions are released to [crates.io](https://crates.io/crates/tailcall) with `cargo publish`. 73 | 74 | ## Benchmarks 75 | 76 | There are a few benchmarks available; currently the benchmarks demonstrate that for 77 | single-function tail-recursion, performance is the same as using a loop. Mutual 78 | recursion runs, but suffers penalties. 79 | 80 | ``` 81 | $ cargo bench 82 | Finished bench [optimized] target(s) in 0.05s 83 | Running target/release/deps/tailcall-b55b2bddb07cb046 84 | 85 | running 0 tests 86 | 87 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 88 | 89 | Running target/release/deps/bench-b8ab29e7ebef8d8d 90 | 91 | running 4 tests 92 | test bench_oddness_boom ... bench: 6 ns/iter (+/- 0) 93 | test bench_oddness_loop ... bench: 6 ns/iter (+/- 0) 94 | test bench_oddness_mutrec ... bench: 4,509,915 ns/iter (+/- 7,095,455) 95 | test bench_oddness_rec ... bench: 3 ns/iter (+/- 0) 96 | 97 | test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured; 0 filtered out 98 | ``` 99 | 100 | If the optimization level is set to zero so that `bench_oddness_boom` isn't cleverly 101 | optimized away, it blows the stack as expected: 102 | 103 | ``` 104 | $ RUSTFLAGS="-C opt-level=0" cargo bench _boom 105 | Finished bench [optimized] target(s) in 0.05s 106 | Running target/release/deps/tailcall-b55b2bddb07cb046 107 | 108 | running 0 tests 109 | 110 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 111 | 112 | Running target/release/deps/bench-b8ab29e7ebef8d8d 113 | 114 | running 1 test 115 | 116 | thread 'main' has overflowed its stack 117 | fatal runtime error: stack overflow 118 | ``` 119 | 120 | In fact the same occurs when running `RUSTFLAGS="-C opt-level=0" cargo bench _mutrec` 121 | , indicating mutual recursion can also blow the stack, but the `loop` and tailrec-enabled 122 | single-function, tail-recursive functions enjoy TCO: 123 | 124 | ``` 125 | $ RUSTFLAGS="-C opt-level=0" cargo bench _loop 126 | Finished bench [optimized] target(s) in 0.06s 127 | Running target/release/deps/tailcall-b55b2bddb07cb046 128 | 129 | running 0 tests 130 | 131 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 132 | 133 | Running target/release/deps/bench-b8ab29e7ebef8d8d 134 | 135 | running 1 test 136 | test bench_oddness_loop ... bench: 4,514,730 ns/iter (+/- 7,498,984) 137 | 138 | test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 3 filtered out 139 | 140 | 141 | $ RUSTFLAGS="-C opt-level=0" cargo bench _rec 142 | Finished bench [optimized] target(s) in 0.05s 143 | Running target/release/deps/tailcall-b55b2bddb07cb046 144 | 145 | running 0 tests 146 | 147 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 148 | 149 | Running target/release/deps/bench-b8ab29e7ebef8d8d 150 | 151 | running 1 test 152 | test bench_oddness_rec ... bench: 22,416,962 ns/iter (+/- 16,083,896) 153 | 154 | test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 3 filtered out 155 | ``` 156 | 157 | 158 | ## Contributing 159 | 160 | Bug reports and pull requests are welcome on [GitHub](https://github.com/alecdotninja/tailcall). 161 | 162 | ## License 163 | 164 | Tailcall is distributed under the terms of both the MIT license and the Apache License (Version 2.0). 165 | 166 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and [COPYRIGHT](COPYRIGHT) for details. 167 | -------------------------------------------------------------------------------- /tailcall-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tailcall-impl" 3 | description = "The procedural macro implementation for the tailcall crate" 4 | documentation.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | license.workspace = true 10 | authors.workspace = true 11 | version.workspace = true 12 | edition.workspace = true 13 | 14 | [dependencies] 15 | proc-macro2 = "~1" 16 | quote = "~1" 17 | syn = { version = "~1", features = ["full", "fold"] } 18 | 19 | [lib] 20 | proc-macro = true 21 | -------------------------------------------------------------------------------- /tailcall-impl/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use syn::{parse_quote, Expr, FnArg, Pat, PatIdent, PatType, Signature}; 2 | 3 | pub trait RewriteForBindLater { 4 | fn bind_later(&mut self) -> Vec<(Pat, Expr)>; 5 | } 6 | 7 | impl RewriteForBindLater for Pat { 8 | fn bind_later(&mut self) -> Vec<(Pat, Expr)> { 9 | match self { 10 | Pat::Ident(PatIdent { attrs, subpat: None, mutability, ident, .. }) if attrs.is_empty() => { 11 | vec![ 12 | ( 13 | Pat::Ident(PatIdent { 14 | attrs: vec![], 15 | subpat: None, 16 | by_ref: None, 17 | mutability: mutability.take(), 18 | ident: ident.clone(), 19 | }), 20 | parse_quote! { #ident }, 21 | ) 22 | ] 23 | }, 24 | _ => unimplemented!("tail recursion for functions with more than simple patterns in the argument list is not supported"), 25 | } 26 | } 27 | } 28 | 29 | impl RewriteForBindLater for FnArg { 30 | fn bind_later(&mut self) -> Vec<(Pat, Expr)> { 31 | match self { 32 | FnArg::Typed(PatType { pat, .. }) => pat.bind_later(), 33 | FnArg::Receiver(_) => unimplemented!("tail recursion in methods (functions with `self` in the arguments list) is not supported"), 34 | } 35 | } 36 | } 37 | 38 | impl RewriteForBindLater for Signature { 39 | fn bind_later(&mut self) -> Vec<(Pat, Expr)> { 40 | self.inputs 41 | .iter_mut() 42 | .flat_map(|fn_arg| fn_arg.bind_later()) 43 | .collect() 44 | } 45 | } 46 | 47 | pub trait Binding { 48 | fn tuple_pat(&self) -> Pat; 49 | fn tuple_expr(&self) -> Expr; 50 | } 51 | 52 | impl Binding for Vec<(Pat, Expr)> { 53 | fn tuple_pat(&self) -> Pat { 54 | let pats: Vec<&Pat> = self.iter().map(|(pat, _expr)| pat).collect(); 55 | 56 | parse_quote! { (#(#pats),*) } 57 | } 58 | 59 | fn tuple_expr(&self) -> Expr { 60 | let exprs: Vec<&Expr> = self.iter().map(|(_pat, expr)| expr).collect(); 61 | 62 | parse_quote! { (#(#exprs),*) } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tailcall-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the procedural macro implementation for the [tailcall] crate. 2 | //! It is not designed to be used directly. 3 | //! [tailcall]: https://crates.io/crates/tailcall 4 | 5 | #![deny( 6 | missing_debug_implementations, 7 | missing_copy_implementations, 8 | trivial_casts, 9 | trivial_numeric_casts, 10 | unsafe_code, 11 | unstable_features, 12 | unused_import_braces, 13 | unused_qualifications 14 | )] 15 | 16 | extern crate proc_macro; 17 | 18 | mod helpers; 19 | mod transforms; 20 | 21 | use proc_macro::TokenStream; 22 | use quote::quote; 23 | use syn::{parse_macro_input, ItemFn}; 24 | 25 | /// Transforms a [function definition] so that all recursive calls within the body are 26 | /// guaranteed to use a single stack frame (via [tail recursion]). 27 | /// 28 | /// [function definition]: https://docs.rs/syn/1.0.9/syn/struct.ItemFn.html 29 | /// [tail recursion]: https://en.wikipedia.org/wiki/Tail_call 30 | /// 31 | /// # Example 32 | /// 33 | /// ``` 34 | /// use tailcall_impl::tailcall; 35 | /// 36 | /// fn factorial(input: u64) -> u64 { 37 | /// #[tailcall] 38 | /// fn factorial_inner(accumulator: u64, input: u64) -> u64 { 39 | /// if input > 0 { 40 | /// factorial_inner(accumulator * input, input - 1) 41 | /// } else { 42 | /// accumulator 43 | /// } 44 | /// } 45 | /// 46 | /// factorial_inner(1, input) 47 | /// } 48 | /// ``` 49 | /// 50 | /// # Requirements 51 | /// 52 | /// - All recursive calls must be in [tail form]: 53 | /// 54 | /// ```compile_fail 55 | /// use tailcall_impl::tailcall; 56 | /// 57 | /// #[tailcall] 58 | /// fn factorial(input: u64) -> u64 { 59 | /// if input > 0 { 60 | /// input * factorial(input - 1) 61 | /// // ^^^^^^^ This is not allowed. 62 | /// } else { 63 | /// 1 64 | /// } 65 | /// } 66 | /// ``` 67 | /// 68 | /// - Methods (functions which bind `self` in the arguments list) are not supported: 69 | /// 70 | /// ```compile_fail 71 | /// trait Factorialable { 72 | /// fn factorial(self) -> Self { 73 | /// self.calc_factorial(1) 74 | /// } 75 | /// 76 | /// fn calc_factorial(self, accumulator: u64) -> u64; 77 | /// } 78 | /// 79 | /// impl Factorialable for u64 { 80 | /// #[tailcall] 81 | /// fn calc_factorial(self, accumulator: u64) -> u64 { 82 | /// // ^^^^ This is not allowed. 83 | /// if self > 0 { 84 | /// (self - 1).calc_factorial(self * accumulator) 85 | /// } else { 86 | /// accumulator 87 | /// } 88 | /// } 89 | /// } 90 | /// ``` 91 | /// 92 | /// [tail form]: https://en.wikipedia.org/wiki/Tail_call 93 | #[proc_macro_attribute] 94 | pub fn tailcall(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 95 | let input = parse_macro_input!(tokens as ItemFn); 96 | let output = transforms::apply_fn_tailcall_transform(input); 97 | 98 | TokenStream::from(quote! { 99 | #output 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /tailcall-impl/src/transforms.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | use syn::{fold::Fold, *}; 3 | 4 | use super::helpers::{Binding, RewriteForBindLater}; 5 | 6 | pub fn apply_fn_tailcall_transform(item_fn: ItemFn) -> ItemFn { 7 | FnTailcallTransformer::new().fold_item_fn(item_fn) 8 | } 9 | 10 | struct FnTailcallTransformer; 11 | 12 | impl FnTailcallTransformer { 13 | pub fn new() -> Self { 14 | Self {} 15 | } 16 | } 17 | 18 | impl Fold for FnTailcallTransformer { 19 | fn fold_item_fn(&mut self, item_fn: ItemFn) -> ItemFn { 20 | let ItemFn { 21 | attrs, 22 | vis, 23 | mut sig, 24 | block, 25 | } = item_fn; 26 | 27 | let binding = sig.bind_later(); 28 | let binding_pat = binding.tuple_pat(); 29 | let binding_expr = binding.tuple_expr(); 30 | 31 | let block = apply_fn_tailcall_body_transform(&sig.ident, *block); 32 | 33 | let block = parse_quote! { 34 | { 35 | let mut tailcall_trampoline_state = #binding_expr; 36 | 37 | 'tailcall_trampoline_loop: loop { 38 | let #binding_pat = tailcall_trampoline_state; 39 | return #block; 40 | } 41 | } 42 | }; 43 | 44 | ItemFn { 45 | attrs, 46 | vis, 47 | sig, 48 | block, 49 | } 50 | } 51 | } 52 | 53 | pub fn apply_fn_tailcall_body_transform(fn_name_ident: &Ident, block: Block) -> Block { 54 | FnTailCallBodyTransformer::new(fn_name_ident).fold_block(block) 55 | } 56 | 57 | struct FnTailCallBodyTransformer<'a> { 58 | fn_name_ident: &'a Ident, 59 | } 60 | 61 | impl<'a> FnTailCallBodyTransformer<'a> { 62 | pub fn new(fn_name_ident: &'a Ident) -> Self { 63 | Self { fn_name_ident } 64 | } 65 | 66 | fn try_rewrite_call_expr(&mut self, expr: &Expr) -> Option { 67 | if let Expr::Call(ExprCall { func, args, .. }) = expr { 68 | if let Expr::Path(ExprPath { ref path, .. }) = **func { 69 | if let Some(ident) = path.get_ident() { 70 | if ident == self.fn_name_ident { 71 | let args = self.fold_expr_tuple(parse_quote! { (#args) }); 72 | 73 | return Some(parse_quote! { 74 | { 75 | tailcall_trampoline_state = #args; 76 | continue 'tailcall_trampoline_loop; 77 | } 78 | }); 79 | } 80 | } 81 | } 82 | } 83 | 84 | None 85 | } 86 | 87 | fn try_rewrite_return_expr(&mut self, expr: &Expr) -> Option { 88 | if let Expr::Return(ExprReturn { 89 | expr: Some(expr), .. 90 | }) = expr 91 | { 92 | return self.try_rewrite_expr(expr); 93 | } 94 | 95 | None 96 | } 97 | 98 | fn try_rewrite_expr(&mut self, expr: &Expr) -> Option { 99 | self.try_rewrite_return_expr(expr) 100 | .or_else(|| self.try_rewrite_call_expr(expr)) 101 | } 102 | } 103 | 104 | impl Fold for FnTailCallBodyTransformer<'_> { 105 | fn fold_expr(&mut self, expr: Expr) -> Expr { 106 | self.try_rewrite_expr(&expr) 107 | .unwrap_or_else(|| fold::fold_expr(self, expr)) 108 | } 109 | 110 | fn fold_expr_closure(&mut self, expr_closure: ExprClosure) -> ExprClosure { 111 | // The meaning of the `return` keyword changes here -- stop transforming 112 | expr_closure 113 | } 114 | 115 | fn fold_item_fn(&mut self, item_fn: ItemFn) -> ItemFn { 116 | // The meaning of the `return` keyword changes here -- stop transforming 117 | item_fn 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tailcall/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tailcall" 3 | description.workspace = true 4 | documentation.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | license.workspace = true 10 | authors.workspace = true 11 | version.workspace = true 12 | edition.workspace = true 13 | 14 | [dependencies] 15 | tailcall-impl = { workspace = true } 16 | 17 | [dev-dependencies] 18 | backtrace = "~0.3" 19 | bencher = "~0.1" 20 | 21 | [[bench]] 22 | name = "bench" 23 | harness = false 24 | -------------------------------------------------------------------------------- /tailcall/benches/bench.rs: -------------------------------------------------------------------------------- 1 | use bencher::*; 2 | use tailcall::*; 3 | 4 | fn is_even_loop(x: u128) -> bool { 5 | let mut i: u128 = x; 6 | let mut even = true; 7 | while i > 0 { 8 | i -= 1; 9 | even = !even; 10 | } 11 | even 12 | } 13 | 14 | fn is_odd_loop(x: u128) -> bool { 15 | !is_even_loop(x) 16 | } 17 | 18 | #[tailcall] 19 | fn is_odd_rec_go(x: u128, odd: bool) -> bool { 20 | if x > 0 { 21 | is_odd_rec_go(x - 1, !odd) 22 | } else { 23 | odd 24 | } 25 | } 26 | 27 | fn is_odd_rec(x: u128) -> bool { 28 | is_odd_rec_go(x, false) 29 | } 30 | 31 | #[tailcall] 32 | fn is_odd_rec_res_go(x: u128, odd: Result) -> Result { 33 | if x > 0 { 34 | is_odd_rec_res_go(x - 1, Ok(!odd?)) 35 | } else { 36 | odd 37 | } 38 | } 39 | 40 | fn is_odd_res_rec(x: u128) -> bool { 41 | is_odd_rec_res_go(x, Ok(false)).unwrap() 42 | } 43 | 44 | // Same as `is_odd_rec_go`, but without the tailcall annotation. 45 | fn is_odd_boom_go(x: u128, odd: bool) -> bool { 46 | if x > 0 { 47 | is_odd_boom_go(x - 1, !odd) 48 | } else { 49 | odd 50 | } 51 | } 52 | 53 | fn is_odd_boom(x: u128) -> bool { 54 | is_odd_boom_go(x, false) 55 | } 56 | 57 | #[tailcall] 58 | fn is_even_mutrec(x: u128) -> bool { 59 | if x > 0 { 60 | is_odd_mutrec(x - 1) 61 | } else { 62 | true 63 | } 64 | } 65 | 66 | #[tailcall] 67 | fn is_odd_mutrec(x: u128) -> bool { 68 | if x > 0 { 69 | is_even_mutrec(x - 1) 70 | } else { 71 | false 72 | } 73 | } 74 | 75 | const ODD_TEST_NUM: u128 = 1000000; 76 | 77 | fn bench_oddness_loop(b: &mut Bencher) { 78 | let mut val: u128 = ODD_TEST_NUM; 79 | b.iter(|| { 80 | is_odd_loop(val); 81 | val += 1; 82 | }); 83 | } 84 | 85 | fn bench_oddness_rec(b: &mut Bencher) { 86 | let mut val: u128 = ODD_TEST_NUM; 87 | b.iter(|| { 88 | is_odd_rec(val); 89 | val += 1; 90 | }); 91 | } 92 | 93 | fn bench_oddness_res_rec(b: &mut Bencher) { 94 | let mut val: u128 = ODD_TEST_NUM; 95 | b.iter(|| { 96 | is_odd_res_rec(val); 97 | val += 1; 98 | }); 99 | } 100 | 101 | fn bench_oddness_boom(b: &mut Bencher) { 102 | let mut val: u128 = ODD_TEST_NUM; 103 | b.iter(|| { 104 | is_odd_boom(val); 105 | val += 1; 106 | }); 107 | } 108 | 109 | fn bench_oddness_mutrec(b: &mut Bencher) { 110 | let mut val: u128 = ODD_TEST_NUM; 111 | b.iter(|| { 112 | is_odd_mutrec(val); 113 | val += 1; 114 | }); 115 | } 116 | 117 | benchmark_group!( 118 | benches, 119 | bench_oddness_loop, 120 | bench_oddness_rec, 121 | bench_oddness_res_rec, 122 | bench_oddness_boom, 123 | bench_oddness_mutrec 124 | ); 125 | 126 | benchmark_main!(benches); 127 | -------------------------------------------------------------------------------- /tailcall/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![deny( 3 | missing_docs, 4 | missing_debug_implementations, 5 | missing_copy_implementations, 6 | trivial_casts, 7 | trivial_numeric_casts, 8 | unsafe_code, 9 | unstable_features, 10 | unused_import_braces, 11 | unused_qualifications 12 | )] 13 | 14 | //! Tailcall is a library that adds safe, zero-cost [tail recursion] to stable Rust. 15 | //! Eventually, it will be superseded by the [`become` keyword]. 16 | //! 17 | //! # Usage 18 | //! 19 | //! To guarantee that recursive calls a function will reuse the same stack frame, 20 | //! annotate it with the [`tailcall`] attribute. 21 | //! 22 | //! ``` 23 | //! use tailcall::tailcall; 24 | //! 25 | //! fn factorial(input: u64) -> u64 { 26 | //! #[tailcall] 27 | //! fn factorial_inner(accumulator: u64, input: u64) -> u64 { 28 | //! if input > 0 { 29 | //! factorial_inner(accumulator * input, input - 1) 30 | //! } else { 31 | //! accumulator 32 | //! } 33 | //! } 34 | //! 35 | //! factorial_inner(1, input) 36 | //! } 37 | //! ``` 38 | //! 39 | //! Recursive calls which are not in tail form will result in a compile-time error. 40 | //! 41 | //! ```compile_fail 42 | //! use tailcall::tailcall; 43 | //! 44 | //! #[tailcall] 45 | //! fn factorial(input: u64) -> u64 { 46 | //! if input > 0 { 47 | //! input * factorial(input - 1) 48 | //! } else { 49 | //! 1 50 | //! } 51 | //! } 52 | //! ``` 53 | //! 54 | //! [tail recursion]: https://en.wikipedia.org/wiki/Tail_call 55 | //! [`become` keyword]: https://internals.rust-lang.org/t/pre-rfc-explicit-proper-tail-calls/3797/16 56 | //! [`tailcall`]: attr.tailcall.html 57 | 58 | pub use tailcall_impl::tailcall; 59 | 60 | pub mod trampoline; 61 | -------------------------------------------------------------------------------- /tailcall/src/trampoline.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a simple, zero-cost [trampoline]. It is designed to be used by the 2 | //! [`tailcall`] macro, but it can also be used manually. 3 | //! 4 | //! # Usage 5 | //! 6 | //! Express the contents of a recusive function as a step function (`Fn(Input) -> Next`). 7 | //! To guarantee that only a single stack frame will be used at all levels of optimization, annotate it 8 | //! with `#[inline(always)]` attribute. This step function and an initial input can then be passed to 9 | //! [`run`] which will recusively call it until it resolves to an output. 10 | //! 11 | //! ``` 12 | //! // fn gcd(a: u64, b: u64) -> u64 { 13 | //! // if b == 0 { 14 | //! // a 15 | //! // } else { 16 | //! // gcd(b, a % b) 17 | //! // } 18 | //! // } 19 | //! 20 | //! #[inline(always)] 21 | //! fn gcd_step((a, b): (u64, u64)) -> tailcall::trampoline::Next<(u64, u64), u64> { 22 | //! if b == 0 { 23 | //! tailcall::trampoline::Finish(a) 24 | //! } else { 25 | //! tailcall::trampoline::Recurse((b, a % b)) 26 | //! } 27 | //! } 28 | //! 29 | //! fn gcd(a: u64, b: u64) -> u64 { 30 | //! 31 | //! tailcall::trampoline::run(gcd_step, (a, b)) 32 | //! } 33 | //! ``` 34 | //! 35 | //! [trampoline]: https://en.wikipedia.org/wiki/Tail_call#Through_trampolining 36 | //! [`tailcall`]: ../tailcall_impl/attr.tailcall.html 37 | //! [`run`]: fn.run.html 38 | //! 39 | 40 | /// This is the output of the step function. It indicates to [run] what should happen next. 41 | /// 42 | /// [run]: fn.run.html 43 | #[derive(Debug)] 44 | pub enum Next { 45 | /// This variant indicates that the step function should be run again with the provided input. 46 | Recurse(Input), 47 | 48 | /// This variant indicates that there are no more steps to be taken and the provided output should be returned. 49 | Finish(Output), 50 | } 51 | 52 | pub use Next::*; 53 | 54 | /// Runs a step function aginast a particular input until it resolves to an output. 55 | #[inline(always)] 56 | pub fn run(step: StepFn, mut input: Input) -> Output 57 | where 58 | StepFn: Fn(Input) -> Next, 59 | { 60 | loop { 61 | match step(input) { 62 | Recurse(new_input) => { 63 | input = new_input; 64 | continue; 65 | } 66 | Finish(output) => { 67 | break output; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tailcall/tests/correctness.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use tailcall::*; 3 | 4 | fn factorial(input: u64) -> u64 { 5 | #[tailcall] 6 | fn factorial_inner(accumulator: u64, input: u64) -> u64 { 7 | if input > 0 { 8 | factorial_inner(accumulator * input, input - 1) 9 | } else { 10 | accumulator 11 | } 12 | } 13 | 14 | factorial_inner(1, input) 15 | } 16 | 17 | #[test] 18 | fn test_factorial_correctness() { 19 | assert_eq!(factorial(0), 1); 20 | assert_eq!(factorial(1), 1); 21 | assert_eq!(factorial(2), 2); 22 | assert_eq!(factorial(3), 6); 23 | assert_eq!(factorial(4), 24); 24 | } 25 | 26 | fn memoized_factorial(input: u64, memo: &mut HashMap) -> u64 { 27 | #[tailcall] 28 | fn factorial_inner(accumulator: u64, input: u64, memo: &mut HashMap) -> u64 { 29 | memo.insert(input, accumulator); 30 | 31 | if input > 0 { 32 | factorial_inner(accumulator * input, input - 1, memo) 33 | } else { 34 | accumulator 35 | } 36 | } 37 | 38 | factorial_inner(1, input, memo) 39 | } 40 | 41 | #[tailcall] 42 | #[allow(dead_code)] 43 | fn add_iter<'a, I>(mut int_iter: I, accum: i32) -> i32 44 | where 45 | I: Iterator, 46 | { 47 | match int_iter.next() { 48 | Some(i) => add_iter(int_iter, accum + i), 49 | None => accum, 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_memoized_factorial_correctness() { 55 | let mut memo = HashMap::new(); 56 | 57 | assert_eq!(memoized_factorial(4, &mut memo), 24); 58 | assert_eq!(memo.get(&0), Some(&24)); 59 | assert_eq!(memo.get(&1), Some(&24)); 60 | assert_eq!(memo.get(&2), Some(&12)); 61 | assert_eq!(memo.get(&3), Some(&4)); 62 | assert_eq!(memo.get(&4), Some(&1)); 63 | } 64 | -------------------------------------------------------------------------------- /tailcall/tests/correctness_option.rs: -------------------------------------------------------------------------------- 1 | use tailcall::*; 2 | 3 | /// Factorial artificial wrapped in a Option 4 | fn factorial(input: u64) -> Option { 5 | #[tailcall] 6 | fn factorial_inner(accumulator: Option, input: Option) -> Option { 7 | let inp = input?; 8 | let acc = accumulator?; 9 | if inp > 0 { 10 | factorial_inner(Some(acc * inp), Some(inp - 1)) 11 | } else { 12 | Some(acc) 13 | } 14 | } 15 | 16 | factorial_inner(Some(1), Some(input)) 17 | } 18 | 19 | #[tailcall] 20 | #[allow(dead_code)] 21 | fn add_iter<'a, I>(mut int_iter: I, accum: i32) -> Option 22 | where 23 | I: Iterator, 24 | { 25 | match int_iter.next() { 26 | Some(i) => add_iter(int_iter, accum + i), 27 | None => Some(accum), 28 | } 29 | } 30 | 31 | #[test] 32 | fn factorial_option_runs() { 33 | assert_eq!(factorial(0).unwrap(), 1); 34 | assert_eq!(factorial(1).unwrap(), 1); 35 | assert_eq!(factorial(2).unwrap(), 2); 36 | assert_eq!(factorial(3).unwrap(), 6); 37 | assert_eq!(factorial(4).unwrap(), 24); 38 | } 39 | -------------------------------------------------------------------------------- /tailcall/tests/correctness_result.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use tailcall::*; 3 | 4 | /// Factorial artificial wrapped in a Result 5 | fn factorial(input: u64) -> Result { 6 | #[tailcall] 7 | fn factorial_inner( 8 | accumulator: Result, 9 | input: Result, 10 | ) -> Result { 11 | let inp = input?; 12 | let acc = accumulator?; 13 | if inp > 0 { 14 | factorial_inner(Ok(acc * inp), Ok(inp - 1)) 15 | } else { 16 | Ok(acc) 17 | } 18 | } 19 | 20 | factorial_inner(Ok(1), Ok(input)) 21 | } 22 | 23 | #[tailcall] 24 | #[allow(dead_code)] 25 | fn add_iter<'a, I>(mut int_iter: I, accum: i32) -> Result 26 | where 27 | I: Iterator, 28 | { 29 | match int_iter.next() { 30 | Some(i) => add_iter(int_iter, accum + i), 31 | None => Ok(accum), 32 | } 33 | } 34 | 35 | #[test] 36 | fn factorial_result_runs() { 37 | assert_eq!(factorial(0).unwrap(), 1); 38 | assert_eq!(factorial(1).unwrap(), 1); 39 | assert_eq!(factorial(2).unwrap(), 2); 40 | assert_eq!(factorial(3).unwrap(), 6); 41 | assert_eq!(factorial(4).unwrap(), 24); 42 | } 43 | -------------------------------------------------------------------------------- /tailcall/tests/stack_depth.rs: -------------------------------------------------------------------------------- 1 | use backtrace::Backtrace; 2 | use tailcall::*; 3 | 4 | #[tailcall] 5 | fn tail_recurse_then_call(times: usize, callback: Callback) { 6 | if times > 0 { 7 | tail_recurse_then_call(times - 1, callback) 8 | } else { 9 | callback() 10 | } 11 | } 12 | 13 | fn current_stack_depth() -> usize { 14 | Backtrace::new_unresolved().frames().len() 15 | } 16 | 17 | #[test] 18 | fn test_tail_recusive_calls_do_not_result_in_new_stack_frames() { 19 | let stack_depth_before_recursion = current_stack_depth(); 20 | 21 | tail_recurse_then_call(100, move || { 22 | let stack_depth_after_recursion = current_stack_depth(); 23 | let change_in_stack_depth = stack_depth_after_recursion - stack_depth_before_recursion; 24 | 25 | // There should be at most one frame for `tail_recurse_then_call` and one frame for the 26 | // callback itself; however, there _may_ be fewer if the compiler decides to inline. 27 | assert!(change_in_stack_depth <= 2); 28 | }); 29 | } 30 | --------------------------------------------------------------------------------