commit 85b08fe00b61992cf181962cfc0e1535d7d78dbd Author: Aleksandr Date: Tue Oct 15 19:03:39 2024 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..178a70b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,66 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "shiny" +version = "0.1.0" + +[[package]] +name = "shiny-std" +version = "0.1.0" +dependencies = [ + "fxhash", + "hashbrown", +] + +[[package]] +name = "shiny-typing" +version = "0.1.0" +dependencies = [ + "shiny-std", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..030771d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "shiny" +version = "0.1.0" +edition = "2021" + +[workspace] +members = [ + "crates/typing", + "crates/std", +] + +[workspace.dependencies] +shiny-typing.path = "crates/typing" +shiny-std.path = "crates/std" + +fxhash = "0.2.1" +hashbrown = "0.15.0" + +[dependencies] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..75b855d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Aleksandr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1f09d4 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Shiny + +Expressive systems language. Inspired by Rust, but different, "shiny" is kinda different than "rust". + +# Design + +1. Types and layouts are different things. Type defines +**what** data is, **layout** defines how type laid in memory. + +2. Typing discipline is structural, making language easier to use +for application programming. Most hugely focus on data and its properties, like +"the field xxx" needs to contain date between 2000.01.01 and 2027.01.01 or +"the token in request is valid". Clearly, these patterns can be implemented without +structural typing by using functions or traits, structural discipline makes it easier to write and +easier to introspect, since this allows to make narrower definition +of predicate in your domain, while passed function logic can be really arbitrary, it's more general, +and you have to deal with that. + +3. Reusing pattern matching for types. In Rust, traits are used to constrain types, +in `shiny`, we reuse `pattern matching` to that kind of thing. So, all bounds are encoded +as "types fits in this shape" rather than as "the type implements these traits". + +# Some notes + +`shiny` has product and sum types, I call these "binary" types - combination of two types. As an example: +`i32 * i32`, alternatively can be written as `(i32, i32)`, also (1) `i32 * i32 * i32 * i32` +(2) `(i32, (i32, (i32, i32)))` and `(i32, i32, i32, i32)` are the same types, but (3) `i32 * (i32 * i32) * i32` are NOT, multiplication +operator here is left-associative, but all (1, 2, 3) types mentioned here are intechangable. Interchangable means here: +If we have constrained type with 1 or 2 or 3 (disambiguation: `or` here means exclusive `or`) **pattern**, +all mentioned types (1, 2, 3) will fulfill the constraint, by applying associativity and commutativity. + +Necessary to note, if we have constraint, say, `i32 * str` and type `str * i32 * bool * i32 * str`, what should compiler choose? +Compiler simply would prefer part of the type that can be moved to head with less number of expression +rewrites. Less strict - we prefer types that are in head, rather than types that are in tail of the type. + diff --git a/crates/std/Cargo.toml b/crates/std/Cargo.toml new file mode 100644 index 0000000..f391d34 --- /dev/null +++ b/crates/std/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "shiny-std" +version = "0.1.0" +edition = "2021" + +[dependencies] +fxhash.workspace = true +hashbrown.workspace = true diff --git a/crates/std/src/arena/intern.rs b/crates/std/src/arena/intern.rs new file mode 100644 index 0000000..3d29a82 --- /dev/null +++ b/crates/std/src/arena/intern.rs @@ -0,0 +1,147 @@ +//! # Interning arena. +//! +//! Additionally, if same value was present on the arena, doesn't add new object, returns old. + +use std::hash::{BuildHasherDefault, Hash}; + +use hashbrown::hash_map::{HashMap, RawEntryMut}; + +use super::{ptr::Ptr, wonly}; + +// No raw_entry API on hashset. +type Bi

= HashMap, (), BuildHasherDefault>; + +#[derive(Debug, Default, Clone, Copy)] +struct Mapping

{ + hash: u64, + key: P, +} + +impl

Hash for Mapping

{ + fn hash(&self, state: &mut H) { + // [`hasher::NoHash`] implementation ensures that + // hasher stores `write_u64` result only once. + state.write_u64(self.hash); + } +} + +#[derive(Clone)] +pub struct Arena { + objects: wonly::Arena, + bi: Bi

, +} + +impl Default for Arena { + fn default() -> Self { + Self { + objects: wonly::Arena::new(), + bi: Bi::default(), + } + } +} + +impl Arena { + pub fn modify(&mut self, ptr: P, f: impl FnOnce(&mut T)) -> bool + where + T: Hash + Eq, + P: Ptr, + { + let Some(object) = self.objects.get(ptr.clone()) else { + return false; + }; + let hash = fxhash::hash64(object); + + let entry = self + .bi + .raw_entry_mut() + .from_hash(hash, |m| self.objects.get(m.key.clone()) == Some(object)); + + // SAFETY: if we got here, the object is in arena. + let object = unsafe { self.objects.get_mut(ptr.clone()).unwrap_unchecked() }; + f(object); + + let hash = fxhash::hash64(object); + entry.and_modify(|k, _| *k = Mapping { hash, key: ptr }); + + true + } + + pub fn get(&self, ptr: P) -> Option<&T> + where + P: Ptr, + { + self.objects.get(ptr) + } + + pub fn insert(&mut self, object: T) -> P + where + P: Ptr, + T: Hash + Eq, + { + let hash = fxhash::hash64(&object); + match self.bi.raw_entry_mut().from_hash(hash, |m| { + self.objects + .get(m.key.clone()) + .map_or(false, |lhs| lhs == &object) + }) { + RawEntryMut::Occupied(occup) => occup.into_key().key.clone(), + RawEntryMut::Vacant(vacant) => { + let key = self.objects.insert(object); + vacant.insert_hashed_nocheck( + hash, + Mapping { + hash, + key: key.clone(), + }, + (), + ); + + key + } + } + } + + pub fn new() -> Self { + Self::default() + } +} + +mod hasher; + +#[cfg(test)] +mod tests { + use super::*; + use crate::arena::ptr::define; + + define! { + struct Token; + } + + #[test] + fn modification_works() { + // Ensure that when modifying the value, + // pointer is still valid. + + let mut arena: Arena = Arena::new(); + let key100to200 = arena.insert(100); + let modified = arena.modify(key100to200, |x| *x = 200); + + assert!(modified); + assert_eq!(arena.get(key100to200), Some(&200)); + + let key100new = arena.insert(100); + assert_ne!(key100to200, key100new); + + assert_eq!(arena.get(key100new), Some(&100)); + assert_eq!(arena.get(key100to200), Some(&200)); + } + + #[test] + fn it_interns() { + let mut arena: Arena = Arena::new(); + let key100 = arena.insert(100); + let key100again = arena.insert(100); + + assert_eq!(key100, key100again); + } +} diff --git a/crates/std/src/arena/intern/hasher.rs b/crates/std/src/arena/intern/hasher.rs new file mode 100644 index 0000000..1039357 --- /dev/null +++ b/crates/std/src/arena/intern/hasher.rs @@ -0,0 +1,22 @@ +use std::hash::Hasher; + +#[derive(Default, Clone, Copy, PartialEq, Eq)] +pub struct NoHash(Option); + +impl Hasher for NoHash { + fn write_u64(&mut self, i: u64) { + if self.0.is_some() { + panic!("called write_u64 twice") + } else { + self.0 = Some(i); + } + } + + fn write(&mut self, _: &[u8]) { + unreachable!() + } + + fn finish(&self) -> u64 { + self.0.unwrap() + } +} diff --git a/crates/std/src/arena/macros.rs b/crates/std/src/arena/macros.rs new file mode 100644 index 0000000..66571a6 --- /dev/null +++ b/crates/std/src/arena/macros.rs @@ -0,0 +1,20 @@ +/// Combination of pointers. +#[macro_export] +#[doc(hidden)] +macro_rules! _make_comb { + ($( + $item:item + )*) => {$( + #[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + )] + $item + )*}; +} diff --git a/crates/std/src/arena/mod.rs b/crates/std/src/arena/mod.rs new file mode 100644 index 0000000..b084e6c --- /dev/null +++ b/crates/std/src/arena/mod.rs @@ -0,0 +1,8 @@ +pub mod intern; +pub mod wonly; + +pub mod ptr; + +pub use crate::_make_comb as make_comb; + +mod macros; diff --git a/crates/std/src/arena/ptr.rs b/crates/std/src/arena/ptr.rs new file mode 100644 index 0000000..f137771 --- /dev/null +++ b/crates/std/src/arena/ptr.rs @@ -0,0 +1,59 @@ +use std::{hash::Hash, num::NonZeroU32}; + +#[macro_export] +#[doc(hidden)] +macro_rules! _ptrs { + ($( + $(#[$outer:meta])* + $vis:vis struct $name:ident; + )*) => {$( + $(#[$outer])* + #[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + )] + $vis struct $name(::core::num::NonZeroU32); + + impl $name { + /// Create instance of the pointer. + pub const fn new(v: ::core::num::NonZeroU32) -> Self { + Self(v) + } + + pub const fn get(&self) -> ::core::num::NonZeroU32 { + self.0 + } + } + + impl ::core::convert::From<$name> for ::core::num::NonZeroU32 { + fn from(value: $name) -> Self { + value.get() + } + } + + impl ::core::convert::From<::core::num::NonZeroU32> for $name { + fn from(value: ::core::num::NonZeroU32) -> Self { + Self::new(value) + } + } + + impl $crate::arena::ptr::Ptr for $name { + fn get(&self) -> ::core::num::NonZeroU32 { + self.0 + } + } + )*}; +} + +/// Pointer in arena. +pub trait Ptr: Copy + Eq + Into + From + Hash { + fn get(&self) -> NonZeroU32; +} + +pub use crate::_ptrs as define; diff --git a/crates/std/src/arena/wonly.rs b/crates/std/src/arena/wonly.rs new file mode 100644 index 0000000..127f8c8 --- /dev/null +++ b/crates/std/src/arena/wonly.rs @@ -0,0 +1,60 @@ +//! # Write-only arena, without deletion + +use std::{marker::PhantomData, num::NonZeroU32}; + +use super::ptr::Ptr; + +#[derive(Clone)] +pub struct Arena { + mem: Vec, + _ptr_ty: PhantomData

, +} + +fn calc_idx(v: NonZeroU32) -> usize { + v.get() as usize - 1 +} + +impl Arena { + pub fn get_mut(&mut self, ptr: P) -> Option<&mut T> + where + P: Ptr, + { + self.mem.get_mut(calc_idx(ptr.into())) + } + + pub fn get(&self, ptr: P) -> Option<&T> + where + P: Ptr, + { + self.mem.get(calc_idx(ptr.into())) + } + + pub fn insert(&mut self, obj: T) -> P + where + P: Ptr, + { + let idx = self.mem.len(); + self.mem.push(obj); + + idx.checked_add(1) + .and_then(|v| v.try_into().ok()) + .and_then(NonZeroU32::new) + .expect("shit happens") + .into() + } +} + +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} + +impl Arena { + pub const fn new() -> Self { + Self { + mem: Vec::new(), + _ptr_ty: PhantomData, + } + } +} diff --git a/crates/std/src/collections.rs b/crates/std/src/collections.rs new file mode 100644 index 0000000..f3783f5 --- /dev/null +++ b/crates/std/src/collections.rs @@ -0,0 +1,6 @@ +use std::hash::BuildHasherDefault; + +use fxhash::FxHasher; + +pub type HashMap = hashbrown::HashMap>; +pub type HashSet = hashbrown::HashSet>; diff --git a/crates/std/src/lib.rs b/crates/std/src/lib.rs new file mode 100644 index 0000000..dc94b8d --- /dev/null +++ b/crates/std/src/lib.rs @@ -0,0 +1,3 @@ +pub mod collections; + +pub mod arena; diff --git a/crates/typing/Cargo.toml b/crates/typing/Cargo.toml new file mode 100644 index 0000000..5a34e8a --- /dev/null +++ b/crates/typing/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "shiny-typing" +version = "0.1.0" +edition = "2021" + +[dependencies] +shiny-std.workspace = true diff --git a/crates/typing/src/bin.rs b/crates/typing/src/bin.rs new file mode 100644 index 0000000..69fe165 --- /dev/null +++ b/crates/typing/src/bin.rs @@ -0,0 +1,33 @@ +//! # Binary types + +use shiny_std::arena::make_comb; + +use crate::ptr; + +make_comb! { + /// Type with additionally equipped ID. + pub struct Field { + pub ty: ptr::Ty, + pub id: ptr::Id, + } + + /// Product type. Pretty much just `lhs_ty * rhs_ty`. + /// The following properties must be upheld: + /// + /// 1. lhs * rhs = rhs * lhs <- commutativity + /// 2. (a * b) * c = a * (b * c) <- associativity + pub struct Prod { + pub lhs: ptr::Ty, + pub rhs: ptr::Ty, + } + + /// Sum type. Pretty much just `lhs_ty + rhs_ty`. + /// The following properties must be upheld: + /// + /// 1. lhs + rhs = rhs + rhs <- commutativity + /// 2. (a + b) + c = a + (b + c) <- associativity + pub struct Sum { + pub lhs: ptr::Ty, + pub rhs: ptr::Ty, + } +} diff --git a/crates/typing/src/check/ctx.rs b/crates/typing/src/check/ctx.rs new file mode 100644 index 0000000..7072c46 --- /dev/null +++ b/crates/typing/src/check/ctx.rs @@ -0,0 +1 @@ +pub struct Ctx {} diff --git a/crates/typing/src/check/mod.rs b/crates/typing/src/check/mod.rs new file mode 100644 index 0000000..7a50591 --- /dev/null +++ b/crates/typing/src/check/mod.rs @@ -0,0 +1,4 @@ +//! # Type checking. + +pub mod ctx; +pub mod shape; diff --git a/crates/typing/src/check/shape.rs b/crates/typing/src/check/shape.rs new file mode 100644 index 0000000..3331c7d --- /dev/null +++ b/crates/typing/src/check/shape.rs @@ -0,0 +1,7 @@ +use crate::ptr; + +use super::ctx::Ctx; + +pub fn fits(lhs: ptr::Ty, rhs: ptr::Ty) -> bool { + todo!() +} diff --git a/crates/typing/src/infer/ctx.rs b/crates/typing/src/infer/ctx.rs new file mode 100644 index 0000000..32b9d7f --- /dev/null +++ b/crates/typing/src/infer/ctx.rs @@ -0,0 +1,15 @@ +use shiny_std::arena::intern; + +use crate::{bin, int, ptr}; + +enum Ty { + Prod(bin::Prod), + Sum(bin::Sum), + Field(bin::Field), + Int(int::Int), +} + +/// Inference context. +pub struct Ctx { + tys: intern::Arena, +} diff --git a/crates/typing/src/infer/mod.rs b/crates/typing/src/infer/mod.rs new file mode 100644 index 0000000..3001abf --- /dev/null +++ b/crates/typing/src/infer/mod.rs @@ -0,0 +1,3 @@ +//! # Type inference. + +mod ctx; diff --git a/crates/typing/src/int.rs b/crates/typing/src/int.rs new file mode 100644 index 0000000..dd50a90 --- /dev/null +++ b/crates/typing/src/int.rs @@ -0,0 +1,14 @@ +//! # Integer types. + +/// Sign of the integer. +#[derive(Debug, Clone, Copy)] +pub enum Signedness { + /// The number has sign. + Signed, + /// The number has no sign. + Unsigned, +} + +/// Integer. +#[derive(Debug, Clone, Copy)] +pub struct Int(pub Signedness); diff --git a/crates/typing/src/lib.rs b/crates/typing/src/lib.rs new file mode 100644 index 0000000..63d3b24 --- /dev/null +++ b/crates/typing/src/lib.rs @@ -0,0 +1,10 @@ +pub mod bin; +pub mod un; + +pub mod int; +pub mod pat; + +pub mod ptr; + +pub mod check; +pub mod infer; diff --git a/crates/typing/src/pat.rs b/crates/typing/src/pat.rs new file mode 100644 index 0000000..d24f0d1 --- /dev/null +++ b/crates/typing/src/pat.rs @@ -0,0 +1,32 @@ +use shiny_arena::make_comb; + +use crate::ptr; + +make_comb! { + /// Product type pattern. + pub struct Prod { + pub lhs: ptr::Pat, + pub rhs: ptr::Pat, + } + + /// Sum type pattern. + pub struct Sum { + pub lhs: ptr::Pat, + pub rhs: ptr::Pat, + } + + /// Exact type. Matches only if matched type + /// equals to specified. + pub struct ExactTy(pub ptr::Ty); + + /// Field type. matches `{ name : ty_pat }` + pub struct Field { + pub name: ptr::Id, + pub ty: ptr::Pat, + } + + /// "Value" pattern. Works like [`ExactTy`], but + /// additionally checks whether value of that type is equal + /// to specified value. + pub struct Value(pub ptr::Value); +} diff --git a/crates/typing/src/ptr.rs b/crates/typing/src/ptr.rs new file mode 100644 index 0000000..6f8d0cd --- /dev/null +++ b/crates/typing/src/ptr.rs @@ -0,0 +1,21 @@ +//! # Pointers to needed information. +//! +//! If values are equal, then their pointers are equal. +//! This means that there's unambiguous mapping from id to value and +//! back. + +use shiny_std::arena::ptr::define; + +define! { + /// Pointer to a type. + pub struct Ty; + + /// Pointer to ID. + pub struct Id; + + /// Pointer to a pattern. + pub struct Pat; + + /// Pointer to a *complete* value. + pub struct Value; +} diff --git a/crates/typing/src/un.rs b/crates/typing/src/un.rs new file mode 100644 index 0000000..bb6fefa --- /dev/null +++ b/crates/typing/src/un.rs @@ -0,0 +1 @@ +//! # Unary types diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..55ce606 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1728492678, + "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1728786660, + "narHash": "sha256-qY+1e0o6oV5ySlErhj/dsWsPLWjrMKzq4QI7a1t9/Ps=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "174a8d9cec9e2c23877a7b887c52b68ef0421d8b", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ba9ead9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + description = "Shiny"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { + nixpkgs, + flake-utils, + rust-overlay, + ... + }: + flake-utils.lib.eachDefaultSystem(system: + let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs { inherit system overlays; }; + + rust = rec { + package = pkgs.rust-bin.nightly.latest.default; + devPackage = package.override { + extensions = [ "rust-src" "rust-analyzer" ]; + }; + }; + in + { + devShells.default = pkgs.mkShell { + shellHook = '' + export PS1="(shiny) $PS1" + ''; + buildInputs = [ rust.devPackage ]; + }; + } + ); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}