Nov 5, 2025 Updated on Nov 9, 2025
A quick note about this blog post: This post also serves as a part of my final submission for Google Summer of Code 20251. If it seems a bit heavier on the code and details, understand that this has to serve as a resource to future contributors, and proof of my work over the past 23 weeks.
A Quick Introduction
Frankly, I have no idea how to start this, so I guess I’ll jump straight to the point: I’m Talyn (@glitchlesscode)2, and I’ve been working on adding Witness Generation to cargo-semver-checks as my Google Summer of Code project. You can find the original proposal here, though be aware t…
Nov 5, 2025 Updated on Nov 9, 2025
A quick note about this blog post: This post also serves as a part of my final submission for Google Summer of Code 20251. If it seems a bit heavier on the code and details, understand that this has to serve as a resource to future contributors, and proof of my work over the past 23 weeks.
A Quick Introduction
Frankly, I have no idea how to start this, so I guess I’ll jump straight to the point: I’m Talyn (@glitchlesscode)2, and I’ve been working on adding Witness Generation to cargo-semver-checks as my Google Summer of Code project. You can find the original proposal here, though be aware that some of the focus has changed with time. I will cover this more in the summary of the project’s goals. This project conclusion should hopefully serve as an accurate summary of what has and has not been done, some of the design details and decisions, as well as where to find all of the work I’ve made.
I personally believe that this project has been an overall success, in spite of the semi-prototypal result3. I have achieved creation of a largely operable and easily expandable system, which is frankly a result I am incredibly happy with and proud of.
A Note On Formality
My goal with this blog is always to maintain some level of informality. I don’t want to feel like an artificial being on the other side of the internet, and I find the best way to do this is to simply write in a much more relaxed manner. However, you may find this blog post has an ever so slight increase in effort to even the slighest level of formality from me. Yet, I will still attempt to still balance this with a level informality, it’s simply how I’m most comfortable writing these.
Project Summary
At its core, this project had as a goal to enable witness generation in cargo-semver-checks. Since I’m very aware that sentence would mean next-to-nothing to somebody who has never heard any of that before, I’ve included a little recap below for anybody who needs it.
What Is Witness Generation?
The cargo subcommand cargo-semver-checks is a tool created with the goal of catching breaking changes to rust crates, such that semantic versioning is always respected. This is performed by the tool using the trustfall query engine, a powerful query engine for matching patterns in large datasets. To go in depth as to what exactly trustfall can do could take up it’s own entire blog post, so I won’t cover it all here. However, at surface level, it’s enough to understand that given nearly any set of data, an “adapter” can be written over top of this data, allowing trustfall to rapidly search the data for matching patterns using a consistent query language, which is itself incredibly similar to GraphQL. In our case, we use one of these “adapters” to query the rustdoc created from a given crate, which gives us access to the large amount of type information included in the rustdoc. For our purposes, we write a new query, or “lint” for each possible breaking change. For instance, at the time of writing, cargo-semver-checks has approximately 200 lints currently implemented, which means 200 queries which had to be created. However, we know this process can be fallible, there can be unpredicted edge-cases in the very lints we use to catch other people’s semantic versioning mistakes! This can create instants where lints detect a breaking change where there is none, which is evidently problematic.
Additionally, triggered lints are not always immediately clear as to what the break in the downstream code would actually be (i.e. what it could look like). This is especially problematic for end-users who are likely less familiar with the many ways their code could produce breaking changes. Knowing what the breaking change is, is one thing, knowing exactly what that looks like downstream is another, which can be incredibly helpful information!
The goal of witness generation is to hopefully remedy both of the above issues, as well as one other issue that will be discussed later. So, how can we do that? Well, the baseline thought is as follows: ‘What if we just generate a possible example of the downstream code, and then check?’
This is done by making a template of what breaking downstream code could look like, and then rendering the template using the values extracted by the original lint. This is created as a rust crate, and then is tested twice using cargo check. One time, it is tested using the baseline version of your crate as a dependency, the other time, it uses the current version. This then allows us to validate that the breaking change we expected would happen, as per the lint, has in fact happened.
As a happy side effect, this also serves to solve the second problem, specifically, we can just keep the generated witness crates after generating them. We can then point the end-user to these witnesses as clear proof of how a given breaking change is actually a breaking change.
This has all been done by myself, working under my mentor, Predrag Gruevski, across two separate GitHub repositories, the base cargo-semver-checks repo, as well as my working repo, which is simply a fork of the original.
My Project Goals
I mentioned earlier, the goals for this project have shifted at least to some degree, between the original proposal and now. This is largely due to a mix of these:
- Additional uses for witnesses were added as additional, more important goals than those originally proposed.
- A number of personal life and health problems, of which I am not comfortable sharing publicly, removed a substantial amount of the time with which I could have worked on the project.
- Although a time extension was taken to try to account for that time loss, my work then had to fit in between all my university work. As such, we had to shift scope and goals.
Now, it would still be good to go over the original set of goals that were set for this project, so lets do that. Below are all the original goals from my back in April of this year. They’ve been labelled as either Complete or Incomplete based on how I personally believe these goals were accomplished. Note that none of these are marked as not being done/as being failed, every goal was worked on to some degree or another.
Community Engagement
This was frankly the smallest goal of my proposal. I remembered seeing blog posts from members of GSoC 2024 across various platforms, which I always enjoyed reading. They are also the main reason I even heard about GSoC and considered applying this year, which is something I was hoping to continue on this year. More specifically, I wrote that I was hoping to give updates in the form of blog posts every 3 weeks. Unfortunately, between all the other time stressors, including my GSoC project itself, I did not find the time to make many of those posts. I don’t find much of problem with this goal being incomplete though, it was not essential to the functioning of this project.
Project Research & Planning
This goal was completed quite easily, it largely entailed communicating with my mentor, Predrag, about the expectations and plans for how to layout the system. I would personally say that this was a continuous process over the course of GSoC, even if the main intent was to just have a rock-solid plan coming going into the development time. This project is just one that constantly has factors that could not be easily predicted, and requires constant adaptation.
Fundamental Witness System
At its core, the witness system has a basic fundamental chain of operations, which is added as a simple extension, adjacent to what cargo-semver-checks already does. It just adds an extra step which acts with entirely non-fatal operations, even if they can be fallible. This foundation is fully implemented, and allows for a simple set of additional values in each lint. Namely, it introduces witness queries and witness templates. The former allows for a second, information collecting query to be run on any lints that matched, while the latter is the template which is rendered to create the witness.
Automated Witness Verification & Diagnostics
The witness system’s utility is largely dependent on the presence of automated testing and reporting of any generated witnesses. This goal accounts for that set of features, in which all witness crates are tested using cargo check against both the baseline and current versions of the test crate. After having run these witnesses, witness results are then collected into a single report, which is displayed to the end user along with any of the other end-of-operation reports. Witness checks which error at any step are still collected, including some incredibly minimal diagnostics, and are also reported to the end user.
Adding Witnesses To Many Existing Lints
There was originally a goal to add witnesses to a large number of the existing lints. However, given the scope adjustments this project received, the end goal has shifted away from having widespread witness coverage, and rather to have a few working witnesses in a few different categories. Looking at this statement, I know the immediate assumption is likely that this project was dramatically overscoped. While that is true to some extent, I want to emphasize that the time that could have been used for writing witnesses was instead repurposed to add extra, incredibly useful features, which are covered below.
Witness System Touchups
This goal was largely in place under the assumption that the whole witness system would be less prototypal than it still is. So, touchups and results are continuously occurring, even now, as we work towards a much more production-ready solution. I can’t really say that touchups are complete, since we’re still off from having that in a great state, but I am still incredibly happy with the level of polish on what is either there, or on how we plan to rework the rougher systems following GSoC. I’d also like to make it clear that this incomplete goal is once again donating some its potential dev time to the additional goals added after, which is why it is incomplete.
So, that’s a little summary of all the original project goals, and their results. However, as was repeatedly mentioned, we added some new goals during the process of development, so why don’t we go over those too?
Witness Dependent Lints
Witness dependent linting is a form of linting which creates a way to test for breaking changes that simply could not be reliably confirmed using only the existing trustfall based system. Let’s take two simple examples of changes in code:
// Old version
pub fn function_with_parameter_changed(value: i32) {
todo!();
}
// New version
pub fn function_with_parameter_changed(value: i64) {
todo!();
}
This above example is obviously a breaking change, we’ve changed a parameter from accepting an i32 to accepting an i64! Now look at this example:
// Old version
pub fn explicit_arg_to_impl_trait(value: i64) {
todo!();
}
// New version
pub fn explicit_arg_to_impl_trait(value: impl Into<i64>) {
todo!();
}
Is this a breaking change? No! All we just converted the explicit i64 parameter to Into<i64>, which i64 obviously implements. So, a piece of downstream code might look like this:
fn witness(value: i64) {
explicit_arg_to_impl_trait(value);
}
This would still work both before and after! However, using only trustfall, we can’t really check for edge cases like this, it can only determine “The function signature changed”. Clearly, this isn’t always a breaking change, so how can we test for this correctly? We can do some heavier parsing4 on the rustdoc, generate a witness using that additional information, and then check to see if the witness sees a breaking change. If we then decide to base the overall lint’s result on the result we just found from the witness, we’ve got an experimentally provable success/fail condition.
This is the concept for what witness dependent linting is, which is already in a working state, even if it is a little rough around the edges. The current prototype adds checks for changed parameter types in both standalone functions and in methods in non-trait implementations. This is a fantastic step in the right direction for how to expand cargo-semver-checks, it allows us to test for breakages we just could not even test for before!
Witness Dependent Lint Unit Testing
Evidently, to be able to trust a system that complex, we want a way to test it. cargo-semver-checks already has a snapshot-based testing system, which outputs all matches of a given lint across a wide set of test crates in the repository. Each of these crates are designed to contain specific examples of breaking changes, as well various edge cases to ensure we only report actual breaking changes. I have since added to this system to make witness-dependent lints also run their witnesses during testing scenarios, and only output that which they find to be breaking changes, bringing witness-dependent lints into the existing testing ecosystem.
Witness Dependent Lint Crate Deletion
Witness dependent lints obviously depend on the witness system to provide a final result. In the case that a breaking change is found, it’s smart to just keep the witness around, it performs the job that any other witness already does, on top of supplying a final result to the lint. However, if the witness finds no breaking change, then we don’t need the witness crate anymore, it will just take up space. As such, I implemented a simple system to remove any witness crates. It removes only the files that should be generated by witness checking, and if any error is encountered during this process, it fails safely by disabling witness crate deletion for the remainder of the run, as well as printing a warning to the user. This way, the run can still complete, it just won’t try to delete any more unnecessary witness crates.
A Note On Project Completion
Again, this witness system is clearly not finished, though it’s in a state where much of what needs to be possible to do is implemented in enough of a capacity that it is expandable. However, the system is not yet stable enough to merge into the main branch of the repository. I, along with Predrag, have plans to continue the development and stabilization of the witness system following GSoC. Currently, some of the simpler work has been merged into main, some of it will be merged into feat/witness-based-linting, and at least at the time of writing, a major part is still in a pull request.
Here is where you can find main, my feature branch, and my current outstanding PR, as well as all pull requests I’ve made in this repository.
A Detour For Refactoring
I’d like to shine a light on the fact that a major roadblock in the middle of this project was the necessity to refactor the existing system which generates rustdoc files. Specifically, the previous system posed a problem to retrieving the forms in which both baseline and current version of the test crate are accessed. cargo-semver-checks can work on versions of crates found on crates.io, in another local directory, or in a git revision, as well as just being able to operate on pre-generated rustdoc files. To account for this, rustdoc used to be generated using a dynamically dispatched system, which used a trait that looked like this:
pub(crate) trait RustdocGenerator {
fn load_rustdoc(
&self,
config: &mut GlobalConfig,
generation_settings: super::data_generation::GenerationSettings,
cache_settings: super::data_generation::CacheSettings<()>,
crate_data: &CrateDataForRustdoc<'_>,
) -> Result<VersionedStorage, TerminalError>;
}
This trait was then implemented on variety of rustdoc generator types, one for each way rustdoc can be loaded. However, for witness generation, we want to know whether we are depending on a local copy, or on a crates.io release, since we need to construct a Cargo.toml file with our dependencies for every witness. This type-erasing, black-box approach to generating rustdoc means, however, that this data we want is buried deep in this abstraction. To complicate things even further, passing a rustdoc file as an input is not actually valid for witnesses, since we need to depend on code which exists somewhere, but the rustdoc can be placed anywhere, entirely unrelated to the source code it refers to.
So, we have this black box system which is hiding data we need, although this data is itself not always present for all data sources. We found that the best way to make this work for what we need was to just do a sizeable refactor. The dynamically dispatched trait-based system was moved to an enum/state-machine hybrid, where the state machine represents current processing state, and the enum represents the different variants of data. This refactor allowed much easier access to both that data we needed, and the overall flow of the data during processing, which may have benefits in the future.
Insights & Design Decisions
Some of the rationales and design decisions made during the creation of this system are not always obvious, I’d like to go over some of the bigger or more interesting ones. This is both to just show off what’s been done, but also to help anybody who may wish to help out with furthering the development of witnesses.
Run ID Generation
The witness crates need to be placed somewhere, obviously, and so we place them in ./target/semver-checks/{RUN_ID}-{CRATE_NAME}/{WITNESS_NAME}-{INDEX}, which makes for a very nice organization. However, clearly that means we need a run ID so each run can be uniquely identified. This is done by generating a string of 6 random alphanumeric characters. If you’re wondering what’s all that confusing by this decision, I should point out that the count of random characters (6), may seem quite arbitrary. To explain this number exactly would take far too long, but I recommend looking at The Birthday Problem5. Long story short, 6 random characters with a possible selection set of 62 unique characters gives us sufficient probability to avoid collisions. We aren’t doing anything cryptographic or randomly significant, we don’t need massive unique IDs.
Fallibility
Most errors throughout the rest of cargo-semver-checks are considered fatal, and will cause an early exit. However, witnesses are not absolutely necessary for it to do what it’s intended to do, at least for anything that is not a witness-dependent lint. Witnesses are largely just a nice thing to have for the end-user, and as such, the entire witness system is designed to be as infallible as possible. Errors are continuously collected and propagated upwards as necessary, but it will always try to drive itself forward, through the rest of the witness system. The result is a system which, while it can experience failures, will almost never cause the entire process to panic and exit early.
Query Running
trustfall queries are run originally to find all instances of changes in the public API of a crate which would be considered breaking. These also allow us to export values about those matching cases so we can use them in witness generation and change reporting. However, sometimes we want to extract values which are not explicitly correlated to the reporting of the change, nor to the matching process, and are instead solely used for witness generation. As such, I’ve added an extra step before witness generation, called the witness query. This stage is designed specifically to extract these additional values, only on cases which have matched earlier. Now, it’s important to note that initial query produces a Vec of maps which correlate string keys to the outputted values, one map for every time the lint matched something. However, if we just run the witness query, we can’t be sure it’ll produce the data we want for the same matching case, at least not without repeating work by re-matching the same criteria.
So, instead of running the witness query once total, we run it once per match, where you are expected to design the witness query to speedily match on the desired item by just matching on the importable path of the target item. This is powered by the fact that the witness query can inherit arguments6 from the original lint, meaning we can output defining factors, such as the importable path of the item, and then inherit them as arguments to the witness query. However, this process can technically match multiple items, or no items at all, if the witness query is badly designed. As a result, the witness query will fail with an error if anything other than exactly one match is produced from the witness query. Note that this will only block the creation of that single witness crate, if all other witness queries operate successfully, then all but the one witness crate will continue to be created and tested.
Witness Templating
The witness template system is used to render witnesses using data extracting from the original query merged with the result from the witness query, and uses the handlebars crate, which provides a very flexible method for injecting values into preset strings. This definitely makes designing lints relatively easy, though they may not always make the most sense when just looking at them, unrendered. For instance, you can end up with templates that look a bit like this:
{{#if (or old_unsafe new_unsafe)}}unsafe {{/if}}fn witness({{#each baseline_arg_types}}val{{@index}}: {{this}}{{#unless @last}}, {{/unless}}{{/each}}) {
{{join "::" path}}({{#each baseline_arg_types}}val{{@index}}{{#unless @last}}, {{/unless}}{{/each}});
}
Although this looks frankly kinda disgusting as far as code goes, this results in very clean and correct rust code in the generated witness. That’s the entire rationale for having code that looks like this. It may not look pretty in the lint, but the resulting end-user experience is great.
Witness Crate Splitting
Throughout this entire blog post, I’ve been simplifying something quite notable. Specifically, I’ve been claiming that for each match, a single witness crate is generated. In reality, we actually that we generate two witness crates for each match. The reason this is necessary at all, however, is rooted in how cargo handles Cargo.lock conflicts. Originally, the way that witnesses were created was by having a single crate, with both dependencies in the Cargo.toml as differently named dependencies, which looked something a bit like this:
[package]
name = "witness_name-0"
version = "1.0.0"
edition = "2024"
[lib]
path = "src/lib.rs"
[features]
baseline = []
[dependencies]
baseline = { path = "/path/to/baseline", package = "crate_name" }
current = { path = "/path/to/current", package = "crate_name" }
We would then inject a “prelude” above the generated witness text which re-imported either baseline or current, while also renaming it back to its original name, dependent on if the baseline flag was set or not. This worked great, except for one critical flaw: cargo requires that the Cargo.lock file only ever contains one package with a given name and version. This meant that if the current version of the crate had the same version of the baseline, the witness would always fail to be checked by cargo, since it would run into a conflict, having two crates with identical package names and versions.
Since we don’t want to require that you must update your version between two comparisons, the evident solution is to generate two witness crates, each with identical code, one with the baseline version as a dependency, the other with the current version. This separates the two entirely, making it so once again, we can trust that cargo should be able to accurately check our witness.
Baseline Flag
As was just mentioned, compared to the first iteration of witness checking, we now split our crates into two. Consequently, one might assume that we got rid of that pesky baseline feature flag from our system. While that could be done, I actually decided to keep it around, still only setting it for the baseline test. Keeping this around no longer serves any purpose to enabling correct imports, so what gives? Consider the following baseline code:
pub struct StructWithMethods;
impl StructWithMethods {
pub fn moves_to_trait(value: i64) {
todo!();
}
}
What if we want to write a witness-dependent lint to check for the case where this method, moves_to_trait changes parameter types? We could just check for all diffs on inherent_impls, which are implementations with no trait involved, and try to find functions with the same name, but different signatures, then generate a witness based on the parameter types present. At surface level, this looks like it would work fine, right? However, consider the following change to the above code:
pub struct StructWithMethods;
pub trait TraitWithMethods {
fn moves_to_trait(value: impl Into<i64>);
}
impl TraitWithMethods for StructWithMethods {
fn moves_to_trait(value: impl Into<i64>) {
todo!();
}
}
Is this a breaking change? Yes, technically, since the above code moves the earlier method to a new trait, code calling moves_to_trait would break since they don’t import the trait which now contains that method. However, specifically, is it a breaking change for method parameter type changes? No, because we’re simply changing the explicit i64 parameter to any type which implements Into<i64>, which is the same non-breaking change we explored earlier. However, we want to be able to test if this is actually a breaking change, since we could have changed it to anything else, for instance an i32, which would be a breaking change. If we just use the earlier described matching scheme, however, which only checks the inherent_impl, we would entirely miss this case, and not end up checking it. So, instead, we need to check for all impls, which includes everything implemented from our trait. However, now, if our template is redesigned to simply import any relevant trait, then on the baseline, the witness will fail to compile, because it will try to import a trait that does not exist yet. How can we remedy this? If only there was some way to conditionally import the new trait, but only for the current version... which there is! If we leave the baseline flag in, we can conditionally disable importing the relevant traits by using #[cfg(not(features = "baseline"))]. Suddenly, we can now check to see if this is actually another breaking change, on top of the breaking change caused by moving the method to a trait.
Minimal Diagnostics
The current way that witnesses are implemented, failures collect incredibly few diagnostics, covering just the bases of what is even needed to report the error at all to the user. Why are they so incredibly minimal? The answer is, rather unsurprinsingly, time, and task priority. I simply did not find the time between the rest of the development of the witness system to actually collect anything more than the bare minimum. Ideally this will be improved with time, following GSoC, it simply did not have time now to be implemented.
Lint Logic
Witness-dependent lints are generally expected to also have additional logic accompanying their runs, such as the process of extracting function or method parameters. As such, a new field was added to the lint type, specifically called lint_logic7. As a default, it uses LintLogic::UseStandard, which defines non-dependent lints. Note that these can still have witnesses, just for standard witness checking. Anything with a lint logic of LintLogic::UseWitness, however, is subject to witness-dependent linting, and so the lint result depends on the result of running the witness. If witnesses are not run for the lint, either because they are not enabled for that run, or because the lint does not contain a witness, then it will always be considered as non-breaking.
Witness Logic Rustdoc Parsing
As was mentioned earlier, witness-dependent lints, those marked with lint_logic: LintLogic::UseWitness, will do some heavier parsing to find values that trustfall currently cannot find. The current two variations of this are WitnessLogic::ExtractFuncArgs and WitnessLogic::ExtractMethodArgs, both of which are pretty self explanatory. The current system extracts these values by using rustdoc_types in order to search for matching importable paths, and then to extract the appropriate values from the function or method. However, this is one of the biggest things holding us back from merging witness-dependent linting into main, as it depends on a specific version of rustdoc_types, tying the system to a category of rust versions, those with the specific version of rustdoc which is supported by this version of rustdoc_types. The goal, following the end of GSoC is to try and move to using the already imported trustfall_rustdoc, which itself depends on rustdoc_types, but does so in a version-agnostic manner. More specifically, it has an adapter which operates on a versioned enum of the rustdoc_types values, so being able to depend on this would greatly improve the versatility of the witness logic system.
Witness Result Collection
Something that is rather of note is how the results from witness checking, both for standard and witness logic, is collected. Immediately, it’s visible that the witness system returns a WitnessReport. However, this report only contains some surface level statistics, such as the number of successful and failed witnesses, the time elapsed for witness checking, and if witness crate deletion experienced any errors. However, to actually access more specific details, we use a rather interesting system to conditionally attach result values to lint results. Specifically, it was a struggle to figure out how to link witnesses to the lints they were running for, especially for witness-dependent lints, where the success of the lint depends on the result of the witness checks. As a result, the original LintResult type which is used for reporting the original lints is given a new field which holds Option<WitnessCheckResult>. This value is always set to None by default, but the LintResults are passed by mutable reference to the witness system, allowing for this field to be set by the witness system itself. This may frustrate some people, it honestly frustrates me too, I’m not a fan of the implicit modification of these lint results by the witness system, but it was arguably the method that made the most sense for what we needed. Again, we need to be able to correlate the witness results to a lint, specifically so we can determine the success of witness-dependent lints. However, in the case that witnesses are not run for a given lint, it also needs to be written in a way where this is accounted for, which the modification of the optional field covers quite conveniently.
One way this could be re-written in the future would be to create a PostWitnessResult, which holds a LintResult, and an optional WitnessCheckResult. Then, so long as the logic is modified to always construct and pass around PostWitnessResults, we can easily allow for runs with witnesses disabled, and runs with witnesses enabled, and always get the expected logic, without any weird, implicit mutations of the existing LintResults.
Where To Go From Here?
The witness system is still, obviously a prototype, but massive swaths of the foundation have been laid. Primarily, the rustdoc_types dependency for witness logic is arguably the largest roadblock to moving this forward. If we can find a way to make that work, it’ll be in a much better state already. Additionally, we ideally want to remove and re-write bizarre bits of code, such as the current system to collect witness results. Lastly, just adding more witnesses to as many lints as possible will obviously bring it into a much more stable state, even if only by forcing us to re-write poorly written parts to accommodate the larger scale and presence of witnesses.
Some Personal Thoughts
If you had asked me a week ago how I felt in terms of what I’d accomplished with this project, I would have been incredibly pessimistic. I would’ve told you that I was unhappy with how much time I didn’t get to spend on it, for any reason. Over the writing of this final submission post, however, I’ve come to realize truly how much I’ve accomplished over the past 23 weeks. I’m very proud of what I’ve created for cargo-semver-checks, and for the Rust community as a whole. I think that this is a feature that will truly make the experience when using this already fantastic tool just that much better. I hope you, the reader will agree with me.
Additionally, I’m just incredibly happy with how much I’ve learned over this long journey. I’ve learned so much about this fantastic language, the code that I write in it, and also just a ton about myself. Again, I won’t delve into details about my personal life, but through the struggles that blocked a lot of this development, I’ve learned so much about myself, as well as other people. The biggest thing I’ve still been learning is how much of a struggle it can be to just ask for a helping hand, even if that’s frankly what you need. I would not have been able to make this by myself, it just would not have happened.
I’m incredibly proud of everything I’ve done here, and I’m so excited to continue work, both on this, and all future projects I may delve in to.
Acknowledgments & Thanks
Wrapping things up, I’d like the thank a few people and organizations for their incredible support over this project:
You
I’d first like to thank you, the reader, for being interested in this project, and in my work. Whether you have just seen this for the very first time, or have been aware of its existence over the past 23 weeks, you, taking time out of your day to come read about what I’ve made, means the absolute world to me.
The Rust Foundation
I’d like to thank the Rust Foundation for creating this fantastic language in which this project was created, and for fostering such an amazing community around it. I’d also like to thank specifically Jakub Beránek, and anybody else who may have helped to organize the Rust Foundation’s participation in GSoC 2025, you are all amazing, and obviously, this never would have happened without you.
Google Summer of Code
I’d like to thank the whole team over at Google in charge of the Google Summer of Code for creating such an interesting, and helpful opportunity, both for the organizations which take part, and for contributors just like me who get involved in this fantastic program. I truly hope that this program will continue well into the future, and continue to provide this great opportunity to bring new contributors into open source software development.
Predrag Gruevski
Lastly, but most certainly not least, I’d like to thank my mentor, Predrag. You are so incredibly smart and insightful, yet also incredibly kind and understanding. Having you continually pushing me forward towards success, no matter the case, has been an amazing experience, and I hope to continue working with you in the future. You were always there to help when I found it in me to ask for it, and were always understanding with the many, many problems I experienced over the course of this program. Thank you, truly, it has been an amazing experience to work with you.
2
You can learn more about me here and here, and you can find most of my relevant digital accounts by looking for GlitchlessCode.
3
The final result has not yet been merged into main. This PR for example, is being merged into feat/witness-based-linting, rather than main.
4
This is currently carried out just using direct operation on rustdoc-types values.