{"id":18016896,"url":"https://github.com/fitzgen/scrapmetal","last_synced_at":"2025-03-26T18:32:31.312Z","repository":{"id":57666378,"uuid":"98691807","full_name":"fitzgen/scrapmetal","owner":"fitzgen","description":"Scrap Your Rust Boilerplate","archived":false,"fork":false,"pushed_at":"2017-11-01T16:41:12.000Z","size":48,"stargazers_count":55,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-17T11:59:45.301Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fitzgen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-28T22:10:38.000Z","updated_at":"2025-02-06T07:55:54.000Z","dependencies_parsed_at":"2022-09-26T20:31:45.931Z","dependency_job_id":null,"html_url":"https://github.com/fitzgen/scrapmetal","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fscrapmetal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fscrapmetal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fscrapmetal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fscrapmetal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitzgen","download_url":"https://codeload.github.com/fitzgen/scrapmetal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245713141,"owners_count":20660351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-30T04:19:37.897Z","updated_at":"2025-03-26T18:32:31.038Z","avatar_url":"https://github.com/fitzgen.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `scrapmetal`: Scrap Your Rust Boilerplate\n\n[![Build Status](https://travis-ci.org/fitzgen/scrapmetal.png?branch=master)](https://travis-ci.org/fitzgen/scrapmetal) [![scrapmetal on crates.io](https://img.shields.io/crates/v/scrapmetal.svg)](https://crates.io/crates/scrapmetal) [![scrapmetal on docs.rs](https://docs.rs/scrapmetal/badge.svg)](https://docs.rs/scrapmetal/)\n\nGeneric transformations, queries, and mutations for Rust without the\nboilerplate.\n\nA port of some of the ideas and code\nfrom\n[\"Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming\" by Lämmel and Peyton Jones](https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf) to\nRust.\n\n⚠ Depends on the specialization nightly Rust feature. ⚠\n\n--------------------------------------------------------------------------------\n\nSay we work on some software that models companies, their departments,\nsub-departments, employees, and salaries. We might have some type definitions\nsimilar to this:\n\n```rust\npub struct Company(pub Vec\u003cDepartment\u003e);\n\npub struct Department(pub Name, pub Manager, pub Vec\u003cSubUnit\u003e);\n\npub enum SubUnit {\n    Person(Employee),\n    Department(Box\u003cDepartment\u003e),\n}\n\npub struct Employee(pub Person, pub Salary);\n\npub struct Person(pub Name, pub Address);\n\npub struct Salary(pub f64);\n\npub type Manager = Employee;\npub type Name = \u0026'static str;\npub type Address = \u0026'static str;\n```\n\nOne of our companies has had a morale problem lately, and we want to transform\nit into a new company where everyone is excited to come in every Monday through\nFriday morning. But we can't really change the nature of the work, so we figure\nwe can just give the whole company a 10% raise and call it close enough. This\nrequires writing a bunch of functions with type signatures like `fn(self, k:\nf64) -\u003e Self` for every type that makes up a `Company`, and since we recognize\nthe pattern, we should be good Rustaceans and formalize it with a trait:\n\n```rust\npub trait Increase: Sized {\n    fn increase(self, k: f64) -\u003e Self;\n}\n```\n\nA company with increased employee salaries is made by increasing the salaries of\neach of its departments' employees:\n\n```rust\nimpl Increase for Company {\n    fn increase(self, k: f64) -\u003e Company {\n        Company(\n            self.0\n                .into_iter()\n                .map(|d| d.increase(k))\n                .collect()\n        )\n    }\n}\n```\n\nA department with increased employee salaries is made by increasing its\nmanager's salary and the salary of every employee in its sub-units:\n\n```rust\nimpl Increase for Department {\n    fn increase(self, k: f64) -\u003e Department {\n        Department(\n            self.0,\n            self.1.increase(k),\n            self.2\n                .into_iter()\n                .map(|s| s.increase(k))\n                .collect(),\n        )\n    }\n}\n```\n\nA sub-unit is either a single employee or a sub-department, so either increase\nthe employee's salary, or increase the salaries of all the people in the\nsub-department respectively:\n\n```rust\nimpl Increase for SubUnit {\n    fn increase(self, k: f64) -\u003e SubUnit {\n        match self {\n            SubUnit::Person(e) =\u003e {\n                SubUnit::Person(e.increase(k))\n            }\n            SubUnit::Department(d) =\u003e {\n                SubUnit::Department(Box::new(d.increase(k)))\n            }\n        }\n    }\n}\n```\n\nAn employee with an increased salary, is that same employee with the salary\nincreased:\n\n```rust\nimpl Increase for Employee {\n    fn increase(self, k: f64) -\u003e Employee {\n        Employee(self.0, self.1.increase(k))\n    }\n}\n```\n\nAnd finally, a lone salary can be increased:\n\n```rust\nimpl Increase for Salary {\n    fn increase(self, k: f64) -\u003e Salary {\n        Salary(self.0 * (1.0 + k))\n    }\n}\n```\n\nPretty straightforward.\n\nBut at the same time, that's a *whole* lot of boilerplate. The only interesting\npart that has anything to do with actually increasing salaries is the `impl\nIncrease for Salary`. The rest of the code is just traversal of the data\nstructures. If we were to write a function to rename all the employees in a\ncompany, most of this code would remain the same. Surely there's a way to factor\nall this boilerplate out so we don't have to manually write it all the time?\n\nEnter `scrapmetal`:\n\n```rust\n// Imports\n#[macro_use]\nextern crate scrapmetal_derive;\nextern crate scrapmetal;\nuse scrapmetal::{Everywhere, Transformation};\n\n// Add derive(Term) to type definitions\n#[derive(Term)]\npub struct Company(pub Vec\u003cDepartment\u003e);\n// Etc...\n\n// Define the `increase` transformation\nlet increase = |s: Salary| Salary(s.0 * 1.1);\nlet mut increase = Everywhere::new(Transformation::new(increase));\n\n// Use the `increase` transformation\nlet new_company = increase.transform(old_company);\n```\n\nNothing more required!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fscrapmetal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitzgen%2Fscrapmetal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fscrapmetal/lists"}