mirror of
https://github.com/ItsDrike/itsdrike.com.git
synced 2025-02-23 16:19:02 +00:00
Fix formatting in various posts
This commit is contained in:
parent
523fefed1b
commit
c5b1c9da0a
7 changed files with 64 additions and 50 deletions
|
@ -89,7 +89,7 @@ Consider this code:
|
|||
```
|
||||
|
||||
In the example here, we can see that python keeps a reference count for the empty list object, and in this case, it was
|
||||
3. The list object was referenced by a, b and the argument passed to `sys.getrefcount`. If we didn't have locks,
|
||||
\3. The list object was referenced by a, b and the argument passed to `sys.getrefcount`. If we didn't have locks,
|
||||
threads could attempt to increase the reference count at once, this is a problem because what would actually happen
|
||||
would go something like this:
|
||||
|
||||
|
@ -102,7 +102,7 @@ would go something like this:
|
|||
|
||||
[Treat sections of 2 lines as things happening concurrently]
|
||||
|
||||
You can see that because threads 1 and 2 both read the reference amount from memory at the same time, they read the
|
||||
You can see that because threads 1 and 2 both read the reference amount from memory at the same time, they read the
|
||||
same number, then they've increased it and stored it back without ever knowing that some other thread is also in the
|
||||
process of increasing the reference count but it read the same amount from memory as this process, so even though the
|
||||
first thread stored the updated amount, the 2nd thread also stored the updated amount, except they were the same
|
||||
|
@ -167,6 +167,7 @@ become incorrect in a way that's hard to see during code reviews.
|
|||
## Debugging multi-threaded code
|
||||
|
||||
As an example, this is a multi-threaded code that will pass all tests and yet it is full of bugs:
|
||||
|
||||
```python
|
||||
import threading
|
||||
|
||||
|
@ -183,6 +184,7 @@ for _ in range(5):
|
|||
threading.Thread(target=foo).start()
|
||||
print("Finished")
|
||||
```
|
||||
|
||||
When you run this code, you will most likely get a result that you would expect, but it is possible that you could also
|
||||
get a complete mess, it's just not very likely because the code runs very quickly. This means you can write code
|
||||
multi-threaded code that will pass all tests and still fail in production, which is very dangerous.
|
||||
|
@ -192,6 +194,7 @@ behind every instruction to ensure that it is safe if a switch happens during th
|
|||
it is advised to run the code multiple times because there is a chance of getting the correct result even with this
|
||||
method since it always is one of the possibilities, this is why multi-threaded code can introduce a lot of problems.
|
||||
This would be the code with this "fuzzing" method applied:
|
||||
|
||||
```python
|
||||
import threading
|
||||
import time
|
||||
|
@ -219,6 +222,7 @@ for _ in range(5):
|
|||
threading.Thread(target=foo).start()
|
||||
print("Finished")
|
||||
```
|
||||
|
||||
You may also notice that I didn't just add `fuzz()` call to every line, I've also split the line that incremented
|
||||
counter into 2 lines, one that reads the counter and another one that actually increments it, this is because
|
||||
internally, that's what would be happening it would just be hidden away, so to add a delay between these instructions
|
||||
|
@ -226,6 +230,7 @@ I had to actually split the code like this. This makes it almost impossible to t
|
|||
problem.
|
||||
|
||||
It is possible to fix this code with the use of locks, which would look like this:
|
||||
|
||||
```python
|
||||
import threading
|
||||
|
||||
|
@ -257,13 +262,15 @@ for t in worker_threads:
|
|||
with printer_lock:
|
||||
print("Finished")
|
||||
```
|
||||
|
||||
As we can see, this code is a lot more complex than the previous one, it's not terrible, but you can probably imagine
|
||||
that with a bigger codebase, this wouldn't be fun to manage.
|
||||
|
||||
Not to mention that there is a core issue with this code. Even though the code will work and doesn't actually have any
|
||||
Not to mention that there is a core issue with this code. Even though the code will work and doesn't actually have any
|
||||
bugs, it is still wrong. Why? When we use enough locks in our multi-threaded code, we may end up making it full
|
||||
sequential, which is what happened here. Our code is running synchronously, with huge amount of overhead from the locks
|
||||
that didn't need to be there and the actual code that would've been sufficient looks like this:
|
||||
|
||||
```python
|
||||
counter = 0
|
||||
print("Starting")
|
||||
|
@ -273,6 +280,7 @@ for _ in range(5)
|
|||
print("----------------------")
|
||||
print("Finished")
|
||||
```
|
||||
|
||||
While in this particular case, it may be pretty obvious that there was no need to use threading at all, there are a lot
|
||||
of cases in which it isn't as clear and I have seen some projects with code that could've been sequential but they were
|
||||
already using threading for something else and so they made use of locks and added some other functionality, which made
|
||||
|
|
|
@ -58,18 +58,22 @@ youtube-dl, download the video and then stream it from our machine instead of fr
|
|||
download the file from our server that has now downloaded this video, however that's way too crude.
|
||||
|
||||
There is a much nicer method that we can use, and it is still utilizing pure SSH:
|
||||
|
||||
```sh
|
||||
ssh -f -N -D 1080 user@server
|
||||
```
|
||||
|
||||
This command will start SSH in background (`-f`), it won't run any actual commands (`-N`) and it will be bound to the
|
||||
port 1080 on our machine (`-D`). This means that we can utilize this port as a SOCK and make our server act as SOCKS5
|
||||
proxy. This kind of proxy will even be supported by most web browsers, allowing you to simply specify the address
|
||||
(in our case `127.0.0.1:1080`) and have all traffic go through this external server.
|
||||
|
||||
To test that this connection really does work, we could use the `curl` command like this:
|
||||
|
||||
```sh
|
||||
curl --max-time 3 -x socks5h://127.0.0.1:1080 https://itsdrike.com
|
||||
```
|
||||
|
||||
If we see the HTML code as the output, it means that we've obtained the content of the specified website through our
|
||||
socks5 proxy, that we've established through simple SSH.
|
||||
|
||||
|
@ -93,9 +97,11 @@ around SSH and it will simply utilize SSH in the background, which is also why w
|
|||
server side for this to work properly, as long as we simply have the SSH server running, `sshuttle` will work fine.
|
||||
|
||||
We can use sshuttle with a command like this:
|
||||
|
||||
```sh
|
||||
sudo sshuttle -r user@machine 172.67.161.205/24 -vv
|
||||
```
|
||||
|
||||
Which will forward all traffic destined for the particular address block (the IP/number is called the CIDR notation, it
|
||||
essentially specifies which IPs should be affected depending on the number after /, you can read more about it on
|
||||
[wikipedia](https://wikiless.org/wiki/Classless_Inter-Domain_Routing?lang=en)). In this case, I've specified the IP of
|
||||
|
@ -112,12 +118,14 @@ you need to think about this ahead of time.
|
|||
|
||||
You could also simply redirect the port 22 to something else using iptables instead of having to mess with the SSH
|
||||
config. You would do that with this command:
|
||||
|
||||
```sh
|
||||
sudo iptables -t nat -I PREROUTING -p tcp --dport 1234 -j REDIRECT --to-ports 22
|
||||
```
|
||||
|
||||
This command will make port `1234` act as the SSH port, and you could then access the server by specifying this port
|
||||
instead of the default port in the ssh command:
|
||||
|
||||
```
|
||||
ssh -f -N -D 1080 user@server -p 1234
|
||||
```
|
||||
|
@ -213,9 +221,11 @@ Turns out that even with a security measure as strict as only allowing access to
|
|||
somewhat make our way to our server, by essentially telling it to map all exiting traffic from port 443 to port 22.
|
||||
|
||||
To do this, we would use a command like this:
|
||||
|
||||
```sh
|
||||
ssh -o "ProxyCommand nc -X connect -x proxy_server:3128 our_server_IP 443" user@our_server_IP
|
||||
```
|
||||
|
||||
Here we're essentially sending a proxy command to the web proxy server (listening on port 3128) to through the port 443
|
||||
to our_server_IP and make requests to the SSH's default port (22) on our_server_IP. Making the actual proxy server
|
||||
access our server on port 22.
|
||||
|
@ -235,8 +245,10 @@ really be possible.
|
|||
|
||||
To explain how easy it is to discover something like this, basically all that's needed is to run a single command on
|
||||
that web proxy:
|
||||
|
||||
```sh
|
||||
iptables -t nat -L
|
||||
```
|
||||
|
||||
And look for the output policy destinations. Even though many network admins won't do this, you shouldn't ever risk
|
||||
doing something silly like this, because if you will get discovered, you could get into some serious trouble
|
||||
|
|
|
@ -3,30 +3,30 @@ title: Managing (multiple) git credentials
|
|||
date: 2022-07-27
|
||||
tags: [programming, git]
|
||||
sources:
|
||||
- <https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git>
|
||||
- <https://docs.github.com/en/authentication/connecting-to-github-with-ssh>
|
||||
- <https://www.onwebsecurity.com/configuration/git-on-windows-location-of-global-configuration-file.html>
|
||||
- <https://security.stackexchange.com/questions/90077/ssh-key-ed25519-vs-rsa>
|
||||
- <https://www.shellhacks.com/git-config-username-password-store-credentials/>
|
||||
- <https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage>
|
||||
- <https://engineeringfordatascience.com/posts/how_to_manage_multiple_git_accounts_on_the_same_machine/>
|
||||
- <https://git-scm.com/docs/gitcredentials>
|
||||
- <https://www.baeldung.com/ops/git-configure-credentials>
|
||||
- <https://www.freecodecamp.org/news/manage-multiple-github-accounts-the-ssh-way-2dadc30ccaca/>
|
||||
- <https://blog.bitsrc.io/how-to-use-multiple-git-accounts-378ead121235>
|
||||
- <https://www.freecodecamp.org/news/the-ultimate-guide-to-ssh-setting-up-ssh-keys/>
|
||||
- <https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-config>
|
||||
- <https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git>
|
||||
- <https://docs.github.com/en/authentication/connecting-to-github-with-ssh>
|
||||
- <https://www.onwebsecurity.com/configuration/git-on-windows-location-of-global-configuration-file.html>
|
||||
- <https://security.stackexchange.com/questions/90077/ssh-key-ed25519-vs-rsa>
|
||||
- <https://www.shellhacks.com/git-config-username-password-store-credentials/>
|
||||
- <https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage>
|
||||
- <https://engineeringfordatascience.com/posts/how_to_manage_multiple_git_accounts_on_the_same_machine/>
|
||||
- <https://git-scm.com/docs/gitcredentials>
|
||||
- <https://www.baeldung.com/ops/git-configure-credentials>
|
||||
- <https://www.freecodecamp.org/news/manage-multiple-github-accounts-the-ssh-way-2dadc30ccaca/>
|
||||
- <https://blog.bitsrc.io/how-to-use-multiple-git-accounts-378ead121235>
|
||||
- <https://www.freecodecamp.org/news/the-ultimate-guide-to-ssh-setting-up-ssh-keys/>
|
||||
- <https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-config>
|
||||
changelog:
|
||||
2023-01-30:
|
||||
- Add note about disabling commit signing
|
||||
- Add alternative command for copying on wayland
|
||||
- Fix typos and text wrapping
|
||||
2023-01-30:
|
||||
- Add note about disabling commit signing
|
||||
- Add alternative command for copying on wayland
|
||||
- Fix typos and text wrapping
|
||||
---
|
||||
|
||||
Many people often find initially setting up their git user a bit unclear, especially when it comes to managing multiple
|
||||
git users on a single machine. But even managing credentials for just a single user can be quite complicated without
|
||||
looking into it a bit deeper. Git provides a lot of different options for credential storage, and picking one can be
|
||||
hard without knowing the pros and cons of that option.
|
||||
hard without knowing the pros and cons of that option.
|
||||
|
||||
Even if you already have your git set up, I'd still recommend at least looking at the possible options git has for
|
||||
credential storage, find the method you're using and make sure it's actually secure enough for your purposes. But
|
||||
|
@ -78,10 +78,9 @@ configured account, you can disable it with:
|
|||
```bash
|
||||
git config --local commit.gpgsign false
|
||||
```
|
||||
|
||||
{{< /notice >}}
|
||||
|
||||
|
||||
|
||||
## Git credentials
|
||||
|
||||
User configuration is one thing, but there's another important part of account configuration to consider, that is
|
||||
|
@ -98,7 +97,7 @@ first take a look at the most straight-forward method, which is to store them in
|
|||
# While clonning:
|
||||
git clone https://<USERNAME>:<PASSWORD>@github.com/path/to/repo.git
|
||||
# After initialized repo without any added remote:
|
||||
git remote add origin
|
||||
git remote add origin
|
||||
# On an already clonned repository without the credentials:
|
||||
git remote set-url origin https://<USERNAME>:<PASSWORD>@github.com/path/to/repo.git
|
||||
```
|
||||
|
@ -170,7 +169,7 @@ worried about leaking your **username** (not password) for the git hosting provi
|
|||
If you're using the global configuration, this generally shouldn't be a big concern, since the username won't actually
|
||||
be in the project file unlike with the remote-urls. However if you share a machine with multiple people, you may want
|
||||
to consider securing your global configuration file (`~/.config/git/config`) using your filesystem's permission
|
||||
controls to prevent others from reading it.
|
||||
controls to prevent others from reading it.
|
||||
|
||||
If you're defining contexts in local project's config though, you should be aware that the username will be present in
|
||||
`.git/config`, and sharing this project with others may leak it.
|
||||
|
@ -239,7 +238,6 @@ The cache credential helper will never write your credential data to disk, altho
|
|||
Unix sockets. These sockets are protected using file permissions that are limited to the user who stored them though,
|
||||
so even in multi-user machine, generally speaking, they are secure.
|
||||
|
||||
|
||||
#### Custom credential helpers
|
||||
|
||||
Apart from these default options, you can also use [custom
|
||||
|
@ -344,7 +342,7 @@ recognized. To run this test, you can simply issue this command (should work on
|
|||
ssh -T git@github.com -i ~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
Running this command should produce a welcome message informing you that the connection works.
|
||||
Running this command should produce a welcome message informing you that the connection works.
|
||||
|
||||
If you are unsuccessful, you can run the command in verbose mode in order to get more details on why your connection
|
||||
was not established.
|
||||
|
@ -426,10 +424,10 @@ to remember the username or the password, instead you just need to know the host
|
|||
|
||||
Generally, using SSH keys is the safest approach, but it can also be a bit annoying since it requires you to specify
|
||||
the SSH host for each repository in it's remote url. For that reason, the approach that I would recommend is using
|
||||
git's credential helper system to store your credentials instead.
|
||||
git's credential helper system to store your credentials instead.
|
||||
|
||||
However if you will go with this method, make sure that you're using a personal access token instead of the actual
|
||||
account's password, to limit the permissions an attacker would gain in case your credentials were leaked.
|
||||
account's password, to limit the permissions an attacker would gain in case your credentials were leaked.
|
||||
|
||||
If your git hosting platform doesn't provide access tokens, this method becomes a lot more dangerous to use, since if
|
||||
an attacker would somehow obtain the credentials file from your system, they would be able to gain full access to your
|
||||
|
@ -500,11 +498,11 @@ git config credentials.helper 'store --file=/home/user/.config/git-credentials-w
|
|||
```
|
||||
|
||||
With this approach, you can have your credentials kept in multiple separate credential files, and just mention the path
|
||||
to the file you need for each project.
|
||||
to the file you need for each project.
|
||||
|
||||
Security-wise, this method is better because your username will be kept outside of the project in the referenced git
|
||||
credential file, which should be secured by the file system's permissions to prevent reads from other users. However
|
||||
practicality-wise, it may be a bit more inconvenient to type and even to remember the path to each credential file.
|
||||
practicality-wise, it may be a bit more inconvenient to type and even to remember the path to each credential file.
|
||||
|
||||
### SSH keys instead
|
||||
|
||||
|
|
|
@ -144,4 +144,3 @@ this byte-code with an interpreter. This is also known as Just In Time (JIT) com
|
|||
|
||||
Most languages tend to only be one or the other, but there are a fair few that follow this hybrid implementation, most
|
||||
notably, these are: Java, C#, Python, VB.NET
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ systems is, as the name would imply, manage the database. It controls how the da
|
|||
if there should be some internal compression of this database or things like that.
|
||||
|
||||
Even though there are a lot of choices for DBMS, no matter which one we end up using, on the surface, they will be
|
||||
doing exactly the same thing. Storing tables of data. Each item in the database usually has some *primary key*, which
|
||||
doing exactly the same thing. Storing tables of data. Each item in the database usually has some _primary key_, which
|
||||
is a unique identifier for given column of data. We can also have composite primary keys, where there would be multiple
|
||||
slots which are unique when combined, but don't necessarily need to be unique on their own. We can also use what's
|
||||
called a *foreign key*, which is basically the primary key of something in another database table, separate from the
|
||||
called a _foreign key_, which is basically the primary key of something in another database table, separate from the
|
||||
current one, to avoid data repetition. This would be an example of the data tables that a database could hold:
|
||||
|
||||
Table of students:
|
||||
|
@ -68,8 +68,8 @@ Student Grades:
|
|||
| ... | ... | ... | ... |
|
||||
{{< /table >}}
|
||||
|
||||
Here we can see that the *Student Grades* table doesn't have a standalone unique primary key, like the Students table
|
||||
has, but rather it has a composite primary key, in this case, it's made of 3 columns: *Student*, *Subject* and *Year*.
|
||||
Here we can see that the _Student Grades_ table doesn't have a standalone unique primary key, like the Students table
|
||||
has, but rather it has a composite primary key, in this case, it's made of 3 columns: _Student_, _Subject_ and _Year_.
|
||||
We can also see that rather than defining the whole student all over again, since we already have the students table,
|
||||
we can instead simply use the Student ID from which we can look up the given student from this table
|
||||
|
||||
|
@ -148,4 +148,3 @@ Another use-case for databases is when you need to host the data of the database
|
|||
database, we can simply expose some port and let the DBMS handle interactions with it when our program can simply be
|
||||
making requests to this remote server. This is usually how we handle using databases with servers, but many client-side
|
||||
programs are creating their own local databases and using those, simply because using files is ineffective.
|
||||
|
||||
|
|
|
@ -134,8 +134,8 @@ return list(result.values())
|
|||
To preserve the original elements, we used a dict that held the unique hashable memory ids of our objects as keys and
|
||||
the actual unhashable objects as values. Once we were done, we just returned all of the values in it as a list.
|
||||
|
||||
The result of this would be: `[x, y, 1, 2, "hi", Foo(x=5)]`. *(Note that `x`, `y` and `Foo(x=5)` would actually be
|
||||
printed in the same way, since they're the same class, sharing the same `__repr__`)*. From this output we can clearly
|
||||
The result of this would be: `[x, y, 1, 2, "hi", Foo(x=5)]`. _(Note that `x`, `y` and `Foo(x=5)` would actually be
|
||||
printed in the same way, since they're the same class, sharing the same `__repr__`)_. From this output we can clearly
|
||||
see that even though `x`, `y`, and `Foo(x=5)` are exactly the same thing, sharing the same attributes, they're
|
||||
different objects and therefore they have different memory ids, which means our algorithm didn't remove them, however
|
||||
there is now only one `x`, because the second one was indeed exactly the same object, so that did get removed.
|
||||
|
@ -217,4 +217,3 @@ one if we know which classes will be used there.
|
|||
Even though we do have ways to deal with unhashables, if you're in control of the classes, and they aren't supposed to
|
||||
be mutable, always make sure to add a `__hash__` method to them, so that duplicates can be easily removed in `O(n)`
|
||||
without any complicated inconveniences.
|
||||
|
||||
|
|
|
@ -165,7 +165,6 @@ Here's a list of some definable generic types that are currently present in pyth
|
|||
| Mapping[str, int] | Mapping from `str` keys to `int` values (immutable) |
|
||||
{{< /table >}}
|
||||
|
||||
|
||||
In python, we can even make up our own generics with the help of `typing.Generic`:
|
||||
|
||||
```python
|
||||
|
@ -247,7 +246,7 @@ x: Tuple[Vehicle, ...] = cars
|
|||
# some of the functionalities of cars, so a type checker would complain here
|
||||
x: Tuple[Car, ...] = vehicles
|
||||
|
||||
# In here, both of these assignments are valid because both cars and vehicles will
|
||||
# In here, both of these assignments are valid because both cars and vehicles will
|
||||
# implement all of the logic that a basic `object` class needs. This means this
|
||||
# assignment is also valid for a generic that's covariant.
|
||||
x: Tuple[object, ...] = cars
|
||||
|
@ -288,7 +287,7 @@ x: Callable[[], Car] = get_wolkswagen_car
|
|||
|
||||
# However this wouldn't really make sense the other way around.
|
||||
# We can't assign a function which returns any kind of Car to a variable with is expected to
|
||||
# hold a function that's supposed to return a specific type of a car. This is because not
|
||||
# hold a function that's supposed to return a specific type of a car. This is because not
|
||||
# every car is a WolkswagenCar, we may get an AudiCar from this function, and that may not
|
||||
# support everything WolkswagenCar does.
|
||||
x: Callable[[], WolkswagenCar] = get_car
|
||||
|
@ -371,10 +370,10 @@ def remove_while_used(func: Callable[[Library, Book], None]) -> Callable[[Librar
|
|||
return wrapper
|
||||
|
||||
|
||||
# As we can see here, we can use the `remove_while_used` decorator with the
|
||||
# As we can see here, we can use the `remove_while_used` decorator with the
|
||||
# `read_fantasy_book` function below, since this decorator expects a function
|
||||
# of type: Callable[[Library, Book], None] to which we're assigning
|
||||
# our function `read_fantasy_book`, which has a type of
|
||||
# our function `read_fantasy_book`, which has a type of
|
||||
# Callable[[Library, FantasyBook], None].
|
||||
#
|
||||
# Obviously, there's no problem with Library, it's the same type, but as for
|
||||
|
@ -384,7 +383,7 @@ def remove_while_used(func: Callable[[Library, Book], None]) -> Callable[[Librar
|
|||
# the necessary criteria for a general Book, it just includes some more special
|
||||
# things, but the decorator function won't use those anyway.
|
||||
#
|
||||
# Since this assignment is be possible, it means that Callable[[Library, Book], None]
|
||||
# Since this assignment is be possible, it means that Callable[[Library, Book], None]
|
||||
# is a subtype of Callable[[Library, FantasyBook], None], not the other way around.
|
||||
# Even though Book isn't a subtype of FantasyBook, but rather it's supertype.
|
||||
@remove_while_used
|
||||
|
@ -468,9 +467,9 @@ people: List[Person] = children
|
|||
|
||||
# Since we know that `people` is a list of `Person` type elements, we can obviously
|
||||
# pass it over to `append_adult` function, which takes a list of `Person` type elements.
|
||||
# After we called this fucntion, our list got altered. it now includes an adult, which
|
||||
# After we called this fucntion, our list got altered. it now includes an adult, which
|
||||
# is fine since this is a list of people, and `Adult` type is a subtype of `Person`.
|
||||
# But what also happened is that the list in `children` variable got altered!
|
||||
# But what also happened is that the list in `children` variable got altered!
|
||||
append_adult(people)
|
||||
|
||||
# This will work fine, all people can eat, that includes adults and children
|
||||
|
@ -590,7 +589,7 @@ c: Matrix[Z] = x # INVALID! Matirx isn't contravariant
|
|||
|
||||
In this case, our Matrix generic type is covariant in the element type, meaning that if we have a `Matrix[Y]` type
|
||||
and `Matrix[X]` type, we could assign the `University[Y]` to the `University[X]` type, hence making it it's
|
||||
subtype.
|
||||
subtype.
|
||||
|
||||
We can make this Matrix covariant because it is immutable (enforced by slots and custom setattr logic). This allows
|
||||
this matrix class (just like any other sequence class), to be covariant. Since it can't be altered, this covariance is
|
||||
|
@ -646,7 +645,7 @@ time, I wasn't able to think of anything better.
|
|||
covariant, since otherwise, you'd need to recast your variable manually when defining another type, or copy your
|
||||
whole generic, which would be very wasteful, just to satisfy type-checkers. Less commonly, you can also find it
|
||||
helpful to mark your generics as contravariant, though this will usually not come up, maybe if you're using
|
||||
protocols, but with full standalone generics, it's quite rarely used. Nevertheless, it's important to
|
||||
protocols, but with full standalone generics, it's quite rarely used. Nevertheless, it's important to
|
||||
- Once you've made a typevar covariant or contravariant, you won't be able to use it anywhere else outside of some
|
||||
generic, since it doesn't make sense to use such a typevar as a standalone thing, just use the `bound` feature of a
|
||||
type variable instead, that will define it's upper bound types and any subtypes of those will be usable.
|
||||
|
|
Loading…
Add table
Reference in a new issue