Niko Matsakis
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit
      • Invitee
      • No invitee
    • Publish Note

      Publish Note

      Everyone on the web can find and read all notes of this public team.
      Once published, notes can be searched and viewed by anyone online.
      See published notes
      Please check the box to agree to the Community Guidelines.
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit
Invitee
No invitee
Publish Note

Publish Note

Everyone on the web can find and read all notes of this public team.
Once published, notes can be searched and viewed by anyone online.
See published notes
Please check the box to agree to the Community Guidelines.
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# Async Shiny Future Design Doc Sketches and Notes ## Use async fn anywhere High-level goal: One should be able to write `async fn` anywhere that you can write `fn`. ### Type alias impl trait (TAIT) * **Status:** Development, close to stabilization * Permits `type Foo = impl Trait` at module and impl level * Within the defining scope of `Foo` (module or impl, respectively), the TAIT `Foo` must be used only in certain positions: * fn return types * value of an associated type * [Stabilization report](https://hackmd.io/o9KO-D8aSb6bJgQ1froVTA) * [Project board](https://github.com/rust-lang/wg-traits/projects/4) ### Generic associated types * **Status:** Development, "approaching" stabilization * Permits `type Foo<...>` in traits and impls * [Project board](https://github.com/rust-lang/wg-traits/projects/4) ### Async fn in traits You should be able to write `async fn` in traits and impls: ```rust trait Foo { async fn bar(&self); } impl Foo for MyType { async fn bar(&self) { ... } } ``` This desugars into a GAT + impl Trait: ```rust trait Foo { type Bar<'me>: Future + 'me; fn bar(&self) -> Self::Bar<'_>; } impl Foo for MyType { type Bar<'me> = impl Future + 'me; fn bar(&self) -> Self::Bar<'_> { async move { ... } } } ``` Unresolved design questions: * What is the name of that GAT we introduce? * I called it `Bar` here, but that's somewhat arbitrary, perhaps we want to have some generic syntax for naming the method? * Or for getting the type of the method. * This problem applies equally to other "`-> impl Trait` in trait" scenarios. * [Exploration doc](https://hackmd.io/IISsYc0fTGSSm2MiMqby4A) * Can users easily bound those GATs with `Send`, maybe even in the trait definition? * People are likely to want to say "I want every future produced by this trait to be Send", and right now that is quite tedious. * This applies equally to other "`-> impl Trait` in trait" scenarios. * What about "dyn" traits? * See the sections on "inline" and dyn" async fn in traits below! ### "Inline" async fn in traits Short version: make it possible to have async fn where the state is stored in the `Self` type ([detailed writeup](https://hackmd.io/bKfiVPRpTvyX8JK_Ng2EWA)). This is equivalent to writing a poll function. Like a poll function, it makes the trait dyn safe; it also has the advantage that `Self: Send` implies that the returned future is also `Send`. Remaining challenges: * Primarily bikeshed. How should we designate that an async function is 'inline', and can we come up with a less overloaded name? ### "Dyn" async fn in traits The most basic desugaring of async fn in traits will make the trait not dyn-safe. "Inline" async fn in traits is one way to circumvent that, but it's not suitable for all traits that must be dyn-safe. There are other efficient options: * Return a `Box<dyn Async<...>>` -- but then we must decide if it will be `Send`, right? And we'd like to only do that when using the trait as a `dyn Trait`. Plus it is not compatible with no-std. * This comes down to needing some form of opt-in. This concern applies equally to other "`-> impl Trait` in trait" scenarios. We have looked at revising how "dyn traits" are handled more generally in the lang team on a number of occasions, but [this meeting](https://github.com/rust-lang/lang-team/blob/master/design-meeting-minutes/2020-01-13-dyn-trait-and-coherence.md) seems particularly relevant. In that meeting we were discussing some soundness challenges with the existing dyn trait setup and discussing how some of the directions we might go enabled folks to write their *own* `impl Trait for dyn Trait` impls, thus defining for themselves how the mapping from Trait to dyn Trait. This seems like a key piece of the solution. One viable route might be: * Traits using `async fn` are not, by default, dyn safe. * You can declare how you want it to be dyn safe: * `#[repr(inline)]` * or `#[derive(dyn_async_boxed)]` or some such * to take an `#[async_trait]`-style approach * It would be nice if users can declare their own styles. For example, Matthias247 pointed out that the `Box` used to allocate can be reused in between calls for increased efficiency. * It would also be nice if there's an easy, decent default -- maybe you don't even *have* to opt-in to it if you are not in `no_std` land. ### Recursive async functions Recursive async functions are not currently possible. This is an artifact of how async fns work today: they allocate all the stack space they will ever need in one shot, which cannot be known for recursive functions. The compiler could manage this by inserting a "box" automatically (perhaps with an allow-by-default lint to let you know it's happening); another option would be to have a convenient way to make a "boxed" async fn, such as `box async fn`, and the compiler could suggest inserting this keyword at the appropriate point so that such a function *can* be recursive. (Note that the boxed function would not have to be a `Box<dyn Async>`, it could be a nominal type instead, and thus the only runtime overhead comes from memory allocation.) *Alternatives:* If we were very concerned about this, we could conceivably switch to an optional "arena" for growing the stack, but this scenario seems to come up relatively rarely. ## Easily compose, control schedule Provide a new "building block" for scheduling based on hierarchical futures. This building block should: * Easily permit spawning concurrent or parallel tasks such that the runtime is able to poll them ### New async trait Today's `Future` trait lacks one fundamental capability compared to synchronous code: there is no way to "block" your caller and be sure that the caller will not continue executing until you agree. In synchronous code, you can use a closure and a destructor to achieve this, which is the technique used for things like `rayon::scope` and crossbeam's scoped threads. Async functions are commonly written with borrowed references as arguments: ```rust async fn do_something(db: &Db) { ... } ``` but important utilities like `spawn` and `spawn_blocking` require `'static` tasks. Without "non-cancelable" traits, the only way to circumvent this is with mechanisms like `FuturesUnordered`. Fundamentally the challenge is that Introduce a trait like ```rust trait Async { type Output; /// # Unsafe conditions /// /// * Once polled, cannot be moved /// * Once polled, destructor must execute before memory is deallocated /// * Once polled, must be polled to completion unsafe fn poll( &mut self, context: &mut core::task::Context<'_>, ) -> core::task::Ready<Self::Output>; /// Requests cancellation; poll must still be called. fn request_cancellation( &mut self ); } ``` ### Specialization to bridge old and new traits Introduce "bridge impls" like the following: ```rust impl<F> Async for F where F: Future { } ``` However, we also need the ability for common combinators to implement both `Future` and `Αsync`: ```rust struct Join<A, B> { ... } impl<A, B> Future for Join<A, B> where A: Future, B: Future, { } impl<A, B> Async for Join<A, B> where A: Async, B: Async, { } ``` This creates a problem, because you have multiple routes to implement `Async` for `Join<A, B>` where `A` and `B` are futures. Specialization can be used to resolve this, and it would be a great feature for Rust overall. However, specialization has a number of challenges to overcome. Some related articles: * [Maximally minimal specialization](https://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/) * [Supporting blanket impls in specialization](https://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/) ### Scoped-based API Async functions are commonly written with borrowed references as arguments: ```rust async fn do_something(db: &Db) { ... } ``` but important utilities like `spawn` and `spawn_blocking` require `'static` tasks. Building on non-cancelable traits, we can implement a "scope" API that allows one to introduce an async scope. This scope API should permit one to spawn tasks into a scope, but have various kinds of scopes (e.g., synchronous execution, parallel execution, and so forth). It should ultimately reside in the standard library and hook into different runtimes for scheduling. This will take some experimentation! ```rust let result = std::async_thread::scope(|s| { let job1 = s.spawn(async || { 11 }); let job2 = s.spawn_blocking(|| { 11 }); job1.await + job2.await }).await; assert_eq!(result, 22); ``` #### Side-stepping the nested await problem One goal of scopes is to avoid the "nested await" problem, as described in [Barbara battles buffered streams (BBBS)][BBBS]. The idea is like this: any combinator which merges multiple async pieces of work -- i.e., initiates concurrency -- needs to take a scope parameter. The way to get concurrency, in other words, is to spawn into a scope, and not to construct small "subschedulers". This includes `FuturesUnordered` and `Stream::buffered`, but also more familiar APIs like `join`. By doing this, we ensure that the scheduler is able to poll those concurrent tasks even while the main task is busy doing something else. A good rule of thumb here is that *only the scheduler ever invokes poll*. **All other code just "awaits" things.**[^hard] [^hard]: This is not a hard rule. But invoking poll manually is best regarded as a risky thing to be managed with care -- not only because of the formal safety guarantees, but because of the possibility for "nested await"-style failures. [BBBS]: https://rust-lang.github.io/wg-async-foundations/vision/status_quo/barbara_battles_buffered_streams.html [`buffered`]: https://docs.rs/futures/0.3.15/futures/prelude/stream/trait.StreamExt.html#method.buffered In the case of [BBBS], the problem arises because of `buffered`, which spawns off concurrent work to process multiple connections. This means that the [`buffered`] combinator would take a `scope` parameter to use for spawning: ```rust async fn do_work(database: &Database) { std::async_thread::scope(|s| { let work = do_select(database, FIND_WORK_QUERY)?; std::async_iter::from_iter(work) .map(|item| do_select(database, work_from_item(item))) .buffered(5, scope) .for_each(|work_item| process_work_item(database, work_item)) .await; }).await; } ``` The `join` combinator would likely be replaced with a method on `scope`:[^variadic] [^variadic]: Naturally we would want a variadic variation, or perhaps a method macro. ```rust let (a_result, b_result) = scope.join(a, b).await; ``` This join method would be defined like so: ```rust impl Scope<'s> { async fn join<A, B>(&mut self, a: A, b: B) -> (A::Output, B::Output) where A: Async + 's, B: Async + 's, { (self.spawn(a).await, b.await) } } ``` #### Could there be a convenient way to access the current scope? If we wanted to integrate the idea of scopes more deeply, we could have some way to get access to the current scope and reference its lifetime. Let's imagine we added a keyword `scope`, and we said that its lifetime is `'scope`. One would then be able to do something like the following: Lots of unknowns to work out here, though. For example, suppose you have a function that creates a scope and invokes a closure within. Do we have a way to indicate to the closure that `'scope` in that closure may be different? It starts to feel like simply passing "scope" values may be simpler, and perhaps we need a way to automate the threading of state instead. ### Voluntary cancellation In today's Rust, any async function can be synchronously cancelled at any await point: the code simply stops executing, and destructors are run for any extant variables. This leads to a lot of bugs. (TODO: link to stories) Under systems like [Swift's proposed structured concurrency model](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md), or with APIs like [.NET's CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-5.0), cancellation is "voluntary". What this means is that when a task is cancelled, a flag is set; the task can query this flag but is not otherwise affected. Under structured concurrency systems, this flag is propagated to all chidren (and transitively to their children). [preemption]: https://tokio.rs/blog/2020-04-preemption Voluntary cancellation is a requirement for scopes. If there are parallel tasks executing within a scope, and the scope itself is canceled, those parallel tasks must be joined and halted before the memory for the scope can be freed. One downside of such a system is that cancellation *may not* take effect. We can make it more likely to work by integrating the cancellation flag into the standard library methods, similar to how tokio encourages ["voluntary preemption"][preemption]. This means that file reads and things will start to report errors (`Err(TaskCanceled)`) once the task has been canceled. This has the advantage that it exercises existing error paths and permits recovery. #### Cancellation and `select` The `select` macro chooses from N futures and returns the first one that matches. Today, the others are immediately canceled. This behavior doesn't play especially well with voluntary cancellation. There are a few options here: * We could make `select` signal cancellation for each of the things it is selecting over and then wait for them to finish. * We could also make `select` continue to take `Future` (not `Async`) values, which effectively makes `Future` a "cancel-safe" trait (or perhaps we introduce a `CancelSafe` marker trait that extends `Async`). * This would mean that typical `async fn` could not be given to select, though we might allow people to mark `async fn` as "cancel-safe", in which case they would implement `Future`. They would also not have access to ordinary async fn, though. * Of course, users could spawn a task that calls the function and give the handle to select. ### Async drop Create an `AsyncDrop` trait: ```rust #[repr(inline)] trait AsyncDrop { async fn async_drop(&mut self); } ``` When an async function is compiled and a value is dropped, we will use the "async drop glue". This is analogous to drop glue except that it invokes the `foo.async_drop().await` method where appropriate. There is also a lint for when something that implements `AsyncDrop` is dropped in a synchronous context. #### What happens when you drop an async-drop value in a sync context? We can't stop you from doing that right now and I don't want to encumber this work with trying to crack that nut. (The basic idea would be to allow things to `impl !Drop for X`, and to make `T: Drop` a default rather like `Sized`, but there's lots of details to work out.) Instead, we offer a lint, and we would encourage people implementing `AsyncDrop` to abort or warn or otherwise try to recover as gracefully as they can. The hope is that this is "sufficiently reliable" that the scenario doesn't happen a lot in practice. Not especially satisfying. ## Generic code that is portable across runtimes ### Read and write We need to have `AsyncRead` and `AsyncWrite` traits. These have several design goals: * No poll functions directly exposed (use `async fn`) * Be accessible via `dyn` trait * Permit simultaneous reads/writes One possibility is the design that [CarlLerche proposed](https://gist.github.com/carllerche/5d7037bd55dac1cb72891529a4ff1540), which separates "readiness" from the actual (non-async) methods to acquire the data: ```rust pub struct Interest(...); pub struct Ready(...); impl Interest { pub const READ = ...; pub const WRITE = ...; } #[repr(inline)] pub trait AsyncIo { /// Wait for any of the requested input, returns the actual readiness. /// /// # Examples /// /// ``` /// async fn main() -> Result<(), Box<dyn Error>> { /// let stream = TcpStream::connect("127.0.0.1:8080").await?; /// /// loop { /// let ready = stream.ready(Interest::READABLE | Interest::WRITABLE).await?; /// /// if ready.is_readable() { /// let mut data = vec![0; 1024]; /// // Try to read data, this may still fail with `WouldBlock` /// // if the readiness event is a false positive. /// match stream.try_read(&mut data) { /// Ok(n) => { /// println!("read {} bytes", n); /// } /// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { /// continue; /// } /// Err(e) => { /// return Err(e.into()); /// } /// } /// /// } /// /// if ready.is_writable() { /// // Try to write data, this may still fail with `WouldBlock` /// // if the readiness event is a false positive. /// match stream.try_write(b"hello world") { /// Ok(n) => { /// println!("write {} bytes", n); /// } /// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { /// continue /// } /// Err(e) => { /// return Err(e.into()); /// } /// } /// } /// } /// } /// ``` async fn ready(&mut self, interest: Interest) -> io::Result<Ready>; } pub trait AsyncRead: AsyncIo { fn try_read(&mut self, buf: &mut ReadBuf<'_>) -> io::Result<()>; } pub trait AsyncWrite: AsyncIo { fn try_write(&mut self, buf: &[u8]) -> io::Result<usize>; } ``` This allows users to: * Take `T: AsyncRead`, `T: AsyncWrite`, or `T: AsyncRead + AsyncWrite` Note that it is always possible to ask whether writes are "ready", even for a read-only source; the answer will just be "no" (or perhaps an error). ### Iterator The async iterator trait, like async read and write, can leverage "dyn in traits": ## Use current runtime without generics ### std library spawn ### std library async_io ### std async abstractions ## Open questions and far out ideas ### async as a monomorphization mode Niko has been toying with the far out idea of making it so that any `fn` can be compiled as an "async fn". Put another way, what if there was an implicit "mode" for your function. It could be compiled in synchronous mode *or* asynchronous mode? There would be some functions that are only compatible with one or the other, but a lot of things (e.g., the I/O abstractions in the standard library) could be made compatible with both. What could be amazing here is that it might offer a way for us to do all kinds of things: * To ease the sync/async split * To support `?` inside of iterator combinators (by making the intermediate functions async-- perhaps cancel-safe async) Some challenges: * At some point, we have to detect that your function invokes a "sync only" function and thus cannot be used in an async context. When do we do that? We could do it at monomorphization time, but we've shied away from that. * If we try to do it before monomorphization time, though, are we back to where we started? * This is basically an effect system. ### await syntax Should we require you to use `.await`? After the epic syntax debates we had, wouldn't it be ironic if we got rid of it altogether, as [carllerche has proposed](https://carllerche.com/2021/06/17/six-ways-to-make-async-rust-easier/)? Basic idea: * When you invoke an async function, it could await by default. * You would write `async foo()` to create an "async expression" -- i.e., to get a `impl Async`. * You might instead write `async || foo()`, i.e., create an async closure. Appealing characteristics: * **More analogous to sync code.** In sync code, if you want to defer immediately executing something, you make a closure. Same in async code, but it's an async closure. * **No confusion around remembering to await.** Right now the compiler has to go to some lengths to offer you messages suggesting you insert `.await`. It'd be nice if you just didn't have to remember. * **Room for optimization.** When you first invoke an async function, it can immediately start executing; it only needs to create a future in the event that it suspends. This may also make closures somewhat smaller. * This could be partially achieved by adding an optional method on the trait that compiles a version of the fn meant to be used when it is *immediately awaited*. But there are some downsides: * **Churn.** Introducing a new future trait is largely invisible to users except in that it manifests as version mismatches. Removing the await keyword is a much more visible change. * **Await points are less visible.** There may be opportunity to introduce concurrency and so forth that is harder to spot when reading the code, particularly outside of an IDE. (In Kotlin, which adopts this model, suspend points are visible in the "gutter" of the editor, but this is not visible when reviewing patches on github.) * **Async becomes an effect.** In today's Rust, an "async function" desugars into a traditional function that returns a future. This function is called like any other, and hence it can implement the `Fn` traits and so forth. In this "await-less" Rust, an async function is called differently from other functions, because it induces an await. This means that we need to consider `async` as a kind of "effect" (like `unsafe`) in a way that is not today. ### ?Drop One problem with async drop is that nothing stops you from dropping async values in a sync context. What do we do in that scenario? There isn't a great answer. The problem is that, right now, Rust assumes that *all* values are "droppable". But what if we removed that assumption. You could declare some values as *non-droppable* (e.g., `impl !Drop for Foo`). This would then mean that those values *must* be consumed in some way, presumably via some kind of blessed function that takes ownership of the value. Generic code would then not be able to use those functions unless it was declared as `T: ?Drop`. That's right, another `?` trait -- something we have traditionally shied away from (and with good reason). Lots of sticky questions to answer. What about `dyn Trait`, for example?

Import from clipboard

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.
Upgrade
All
  • All
  • Team
No template found.

Create custom template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

How to use Slide mode

API Docs

Edit in VSCode

Install browser extension

Get in Touch

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Upgrade to Prime Plan

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

No updates to save
Compare with
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Upgrade

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Upgrade

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully